[Java] 使用同步方法而不是同步塊會有好處嗎?


Answers

唯一真正的區別是同步塊可以選擇它同步的對象。 同步方法只能使用'this' (或者對應的類實例用於同步類方法)。 例如,這些在語義上是等同的:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

後者更靈活,因為它可以競爭任何對象的關聯鎖,通常是成員變量。 它也更精細,因為您可以在塊之前和之後執行並發代碼,但仍然在方法中。 當然,通過將並發代碼重構為單獨的非同步方法,您可以輕鬆使用同步方法。 使用任何一種方式使代碼更易於理解。

Question

有沒有人可以告訴我同步方法優於同步塊的例子?




與線程同步。 1)從不使用同步(this)的線程不工作。 與(this)同步使用當前線程作為鎖定線程對象。 由於每個線程都獨立於其他線程,所以沒有協調同步。 2)代碼測試表明,在Mac 1.6上的Java 1.6中,方法同步不起作用。 3)synchronized(lockObj)其中lockObj是所有線程同步的公共共享對象,它將起作用。 4)ReenterantLock.lock()和.unlock()工作。 請參閱Java教程。

以下代碼顯示了這些要點。 它還包含將用於替代ArrayList的線程安全Vector,以顯示添加到Vector的許多線程不會丟失任何信息,而與ArrayList相同的則可能會丟失信息。 0)當前代碼顯示由於競態條件導致的信息丟失A)註釋當前標註的A行,並取消註釋上面的A行,然後運行,方法丟失數據但不應該。 B)反向步驟A,取消註釋B和//結束塊}。 然後運行以查看結果不丟失數據C)註釋掉B,取消註釋C.運行,看到同步(this)丟失數據,如預期的那樣。 沒有時間來完成所有的變化,希望這有助於。 如果在(this)上進行同步或方法同步工作,請說明您測試的Java和OS的版本。 謝謝。

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class



通常在方法級別使用鎖定太粗魯了。 為什麼要通過鎖定整個方法來鎖定一段不訪問任何共享資源的代碼。 由於每個對像都有一個鎖,所以可以創建虛擬對象來實現塊級同步。 塊級別更高效,因為它不鎖定整個方法。

這裡舉一些例子

方法級別

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

塊級

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[編輯]

對於CollectionVectorHashtable它們在ArrayListHashMap不同時同步,並且您需要設置synchronized關鍵字或調用Collections同步方法:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list



大多數情況下,我使用它來同步訪問列表或地圖,但我不想阻止訪問對象的所有方法。

在下面的代碼中,修改列表的一個線程不會阻塞等待修改地圖的線程。 如果方法在對像上同步,那麼即使所做的修改不會發生衝突,每個方法也必須等待。

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}



唯一的區別是: 同步塊允許粒度鎖定,與同步方法不同

基本上synchronized塊或方法已被用於通過避免內存不一致錯誤來編寫線程安全代碼。

這個問題很古老,在過去的7年中很多事情都發生了變化。 已經為線程安全引入了新的編程結構。

您可以通過使用高級並發API而不是synchronied塊來實現線程安全。 這個文檔page提供了很好的編程結構來實現線程安全。

鎖對象支持簡化許多並發應用程序的鎖定習慣用法。

Executors定義了一個用於啟動和管理線程的高級API。 由java.util.concurrent提供的執行器實現提供適用於大規模應用程序的線程池管理。

並發集合可以更輕鬆地管理大量數據,並且可以大大減少同步需求。

原子變量具有最小化同步並有助於避免內存一致性錯誤的功能。

ThreadLocalRandom (在JDK 7中)可以從多個線程高效地生成偽隨機數。

更好地替換同步是ReentrantLock ,它使用Lock API

可重入的互斥鎖具有與使用同步方法和語句訪問的隱式監視器鎖相同的基本行為和語義,但具有擴展功能。

鎖定示例:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

其他編程結構也參考java.util.concurrentjava.util.concurrent.atomic包。

也參考這個相關的問題:

同步與鎖定




從Java規範總結: http://www.cs.cornell.edu/andru/javaspec/17.doc.html : http://www.cs.cornell.edu/andru/javaspec/17.doc.html

synchronized語句(§14.17)計算對象的引用; 它會嘗試對該對象執行鎖定操作,直到鎖定操作成功完成後才會繼續進行。 ...

同步方法(第8.4.3.5節)在調用時自動執行鎖定操作; 在鎖定動作成功完成之前,其主體不會執行。 如果該方法是一個實例方法 ,它會鎖定與調用它的實例相關的鎖(也就是說,在執行方法主體期間將被稱為此的對象)。 如果方法是靜態的 ,它將鎖定與表示定義該方法的類的Class對象關聯的鎖。 ...

基於這些描述,我會說大多數以前的答案是正確的,並且一個同步方法可能對靜態方法特別有用,否則你將不得不弄清楚如何獲得“代表方法類的Class對象定義“。

編輯:我原本以為這些是實際的Java規範的引號。 澄清此頁面只是對規格的總結/說明




主要區別在於,如果使用同步塊,則可以鎖定除此之外的其他對象,從而使其更加靈活。

假設你有一個消息隊列和多個消息生產者和消費者。 我們不希望生產者互相干擾,但消費者應該能夠檢索消息而不必等待生產者。 所以我們只是創建一個對象

Object writeLock = new Object();

從現在開始,每當生產者想要添加新消息時,我們都會鎖定它:

synchronized(writeLock){
  // do something
}

所以消費者仍然可以閱讀,生產者將被鎖定。




正如已經說過的那樣,同步塊可以使用用戶定義的變量作為鎖對象,當同步函數只使用“this”時。 當然,您可以使用應該同步的功能區域進行操作。 但是大家都說使用“this”作為鎖對象的同步函數和覆蓋整個函數的block之間沒有區別。 這是不正確的,區別在於在兩種情況下都會產生的字節碼。 在同步塊使用的情況下,應該分配引用“this”的局部變量。 因此,我們將會為功能提供更大的尺寸(如果您只有少數幾個功能,則不相關)。

更詳細的解釋你可以在這裡找到差異: http://www.artima.com/insidejvm/ed2/threadsynchP.html : http://www.artima.com/insidejvm/ed2/threadsynchP.html




可以使用反射API檢查同步方法。 這對測試某些合同很有用,例如模型中的所有方法都是同步的

以下片段打印所有Hashtable的同步方法:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}



在同步方法的情況下,將在對像上獲取鎖定。 但是,如果您使用同步塊進行操作,您可以選擇指定將獲取鎖的對象。

示例:

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }



注意: 靜態同步方法和塊在Class對像上工作。

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}