2015-01-05 48 views
3

我有一個由兩個線程共享的變量。這兩個線程將對其執行一些操作。我不知道爲什麼每次執行程序時sharedVar的結果都不一樣。同步方法不能按預期方式工作

public class Main 
{ 
    public static int sharedVar = 0; 
    public static void main(String[] args) 
    { 
     MyThread mt1 = new MyThread(); 
     MyThread mt2 = new MyThread(); 
     mt1.start(); 
     mt2.start(); 

     try 
     { 
      // wait for the threads 
      mt1.join(); 
      mt2.join(); 
     } 
     catch (InterruptedException e1) 
     { 
      e1.printStackTrace(); 
     } 

     System.out.println(sharedInt); // I expect this value to be 20000, but it's not 
    } 
} 

下面是類 「MyThread的」

public class MyThread extends Thread 
{ 
    private int times = 10000; 
    private synchronized void addOne() 
    { 
     for (int i = 0; i < times; ++i) 
     { 
      Main.sharedVar ++; 
     } 
    } 

    @Override 
    public void run() 
    { 
     addOne(); 
    } 
} 

sharedVar的最終結果是有時13735,12508,或18793;但從來沒有20000,這是我期望的結果。該計劃的另一個有趣的事情是時間= 1000。作爲最終結果,我總是得到2000。

任何人都可以解釋這種現象嗎?

+0

嘗試使sharedVar揮發性。 –

+0

將volatile添加到sharedVar後,結果接近20000,但仍低於20000. – Brian

+0

易失性單獨無法工作的原因是因爲'Main.sharedVar ++'是一個複合操作(read-increment-write)。揮發性不提供任何保護。 – Kayaman

回答

4

一個synchronized方法保護,這意味着你的代碼等同於資源this

private void addOne() 
{ 
    synchronized(this) 
    { 
     for (int i = 0; i < times; ++i) 
     { 
      Main.sharedVar ++; 
     } 
    } 
} 

但是,你有2名對象進行addOne方法被調用。這意味着this對於mt1.addOne不等於this對於mt2.addOne,因此您沒有共同的同步資源。

嘗試改變YOUT addOne代碼:

private void addOne() 
{ 
    synchronized(MyThread.class) 
    { 
     for (int i = 0; i < times; ++i) 
     { 
      Main.sharedVar ++; 
     } 
    } 
} 

而且你會看到預期的行爲。如下面的註釋所示,最好使用與MyThread.class不同的對象進行同步,因爲類對象可以從多個點訪問,其他代碼可能會嘗試使用同一對象進行同步。

+1

不要鎖定類。使用一些其他對象作爲鎖。 +1無論如何:) – TheLostMind

+0

@TheLostMind是的,你是對的,最好是鎖定其他對象,但我想寫一些草案的代碼,其中一個普通的對象被用作同步資源。 –

+0

我不知道爲什麼「同步(MyThread.class)」和「同步(Main.class)」工作。 – Brian

2

當您在非靜態方法上使用​​時,可以使用當前對象作爲監視器。

當您在靜態方法上使用​​時,可以使用類的當前對象(ClassName.class靜態字段)作爲監視器。

在你的情況下,你使用線程對象上的​​(2個不同的實例),所以兩個不同的線程會同時修改你的sharedVar靜態字段。

你可以用不同的方法修復它。

addOne方法移動到Main並使其成爲static

private static synchronized void addOne(int times) 
{ 
    for (int i = 0; i < times; ++i) 
    { 
     sharedVar++; 
    } 
} 

或者你可以創建類調用SharedVar與現場private int var;和方法synchronized void addOne(int times)和傳遞的SharedVar單個實例您的胎面。

public static void main(String[] args) 
{ 
    SharedVar var = new SharedVar(); 
    MyThread mt1 = new MyThread(var); 
    MyThread mt2 = new MyThread(var); 
    mt1.start(); 
    mt2.start(); 

    try 
    { 
     // wait for the threads 
     mt1.join(); 
     mt2.join(); 
    } 
    catch (InterruptedException e1) 
    { 
     e1.printStackTrace(); 
    } 

    System.out.println(var.getVar()); // I expect this value to be 20000, but it's not 
} 

但是如果你只需要一個整數多個線程改變,你可以使用類從java.til.concurrent.*,像AtomicLongAtomicInteger

0

sharedVar定義爲AtomicLong而不是int。使函數​​可以正常工作,但效率較低,因爲您只需要同步增量。

0

當一個線程將要執行一個「同步實例方法,它aqcuires對對象的鎖(準確地,鎖定該對象監視器上)。

所以在你的情況下,線程mt1獲取對象mt1上的鎖定並且線程mt2獲取對象mt2上的鎖定並且它們不會因爲兩個線程正在兩個不同的鎖上工作而阻塞彼此。

而當兩個線程同時修改共享變量(不同步的方式)時,結果是不可預測的。

那麼關於值1000的情況,對於較小的輸入,交錯執行可能會導致正確的結果(幸運的是)。

溶膠:刪除方法AddOne synchronized關鍵字和使sharedVal爲類型「的AtomicInteger」的

0

立即加入線程啓動方法之後。從這個線程-1開始,並在線程2啓動並進入死亡狀態之後進入死亡狀態。所以它會始終打印您的預期輸出。

變化,如下所示的代碼: -

public class Main{ 

    public static int sharedVar = 0; 

    public static void main(String[] args) 

     { 
      MyThread mt1 = new MyThread(); 
      MyThread mt2 = new MyThread(); 

      try 

       { 
        mt1.start(); 
        mt1.join(); 
        mt2.start(); 
        mt2.join(); 
       } 

      catch (InterruptedException e1) 

       { 
        e1.printStackTrace(); 
       } 

      System.out.println(sharedVar); 

     } 
} 

class MyThread extends Thread 
{ 
    private int times = 1000000; 

    private synchronized void addOne() 
     { 
      for (int i = 0; i < times; ++i) 
       { 
        Main.sharedVar++; 
       } 
     } 

    @Override 
    public void run() 
     { 
      addOne(); 
     } 
} 
相關問題