有人能告诉我同步方法比同步块的优势与一个例子吗?


当前回答

来自Java规范摘要: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

The synchronized statement (§14.17) computes a reference to an object; it then attempts to perform a lock action on that object and does not proceed further until the lock action has successfully completed. ... A synchronized method (§8.4.3.5) automatically performs a lock action when it is invoked; its body is not executed until the lock action has successfully completed. If the method is an instance method, it locks the lock associated with the instance for which it was invoked (that is, the object that will be known as this during execution of the body of the method). If the method is static, it locks the lock associated with the Class object that represents the class in which the method is defined. ...

基于这些描述,我想说以前的大多数答案都是正确的,同步方法可能对静态方法特别有用,否则您必须弄清楚如何获得“表示定义方法的类的Class对象”。

编辑:我原本以为这些是对实际Java规范的引用。澄清一下,本页只是对规范的总结/解释

其他回答

当java编译器将源代码转换为字节码时,它处理同步方法和同步块的方式非常不同。

当JVM执行一个同步方法时,执行线程识别出该方法的method_info结构体设置了ACC_SYNCHRONIZED标志,然后它自动获取对象的锁,调用该方法,并释放锁。如果发生异常,线程自动释放锁。

另一方面,同步方法块绕过了JVM对获取对象锁和异常处理的内置支持,并要求显式地用字节代码编写功能。如果读取带有同步块的方法的字节代码,您将看到十多个额外的操作来管理此功能。

这显示了生成同步方法和同步块的调用:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

synchronizedMethodGet()方法生成以下字节代码:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

下面是synchronizedBlockGet()方法的字节代码:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

One significant difference between synchronized method and block is that, Synchronized block generally reduce scope of lock. As scope of lock is inversely proportional to performance, its always better to lock only critical section of code. One of the best example of using synchronized block is double checked locking in Singleton pattern where instead of locking whole getInstance() method we only lock critical section of code which is used to create Singleton instance. This improves performance drastically because locking is only required one or two times.

在使用同步方法时,如果混合使用静态同步方法和非静态同步方法,则需要格外小心。

来自Java规范摘要: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

The synchronized statement (§14.17) computes a reference to an object; it then attempts to perform a lock action on that object and does not proceed further until the lock action has successfully completed. ... A synchronized method (§8.4.3.5) automatically performs a lock action when it is invoked; its body is not executed until the lock action has successfully completed. If the method is an instance method, it locks the lock associated with the instance for which it was invoked (that is, the object that will be known as this during execution of the body of the method). If the method is static, it locks the lock associated with the Class object that represents the class in which the method is defined. ...

基于这些描述,我想说以前的大多数答案都是正确的,同步方法可能对静态方法特别有用,否则您必须弄清楚如何获得“表示定义方法的类的Class对象”。

编辑:我原本以为这些是对实际Java规范的引用。澄清一下,本页只是对规范的总结/解释

TLDR;不要使用synchronized修饰符或synchronized(this){…}表达式but synchronized(myLock){…其中myLock是一个持有私有对象的最终实例字段。


在方法声明中使用synchronized修饰符与在方法主体中使用synchronized(..){}表达式的区别如下:

The synchronized modifier specified on the method's signature is visible in the generated JavaDoc, is programmatically determinable via reflection when testing a method's modifier for Modifier.SYNCHRONIZED, requires less typing and indention compared to synchronized(this) { .... }, and (depending on your IDE) is visible in the class outline and code completion, uses the this object as lock when declared on non-static method or the enclosing class when declared on a static method. The synchronized(...){...} expression allows you to only synchronize the execution of parts of a method's body, to be used within a constructor or a (static) initialization block, to choose the lock object which controls the synchronized access.

然而,使用synchronized修饰符或synchronized(…){…}使用this作为锁对象(如synchronized(this){…}),也有同样的缺点。两者都使用它自己的实例作为锁对象进行同步。这是很危险的,因为不仅对象本身,而且任何其他持有该对象引用的外部对象/代码也可以将其用作同步锁,这可能会产生严重的副作用(性能下降和死锁)。

因此,最佳实践是既不使用synchronized修饰符,也不使用synchronized(…)表达式作为锁对象,而是使用该对象的私有锁对象。例如:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

您也可以使用多个锁对象,但是需要特别注意,以确保在嵌套使用时不会导致死锁。

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

Synchronizing with threads. 1) NEVER use synchronized(this) in a thread it doesn't work. Synchronizing with (this) uses the current thread as the locking thread object. Since each thread is independent of other threads, there is NO coordination of synchronization. 2) Tests of code show that in Java 1.6 on a Mac the method synchronization does not work. 3) synchronized(lockObj) where lockObj is a common shared object of all threads synchronizing on it will work. 4) ReenterantLock.lock() and .unlock() work. See Java tutorials for this.

The following code shows these points. It also contains the thread-safe Vector which would be substituted for the ArrayList, to show that many threads adding to a Vector do not lose any information, while the same with an ArrayList can lose information. 0) Current code shows loss of information due to race conditions A) Comment the current labeled A line, and uncomment the A line above it, then run, method loses data but it shouldn't. B) Reverse step A, uncomment B and // end block }. Then run to see results no loss of data C) Comment out B, uncomment C. Run, see synchronizing on (this) loses data, as expected. Don't have time to complete all the variations, hope this helps. If synchronizing on (this), or the method synchronization works, please state what version of Java and OS you tested. Thank you.

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

关于使用同步块的重要提示:小心你使用的锁对象!

上面user2277816的代码片段说明了这一点,将字符串字面值的引用用作锁定对象。 意识到字符串字面值在Java中是自动互缩的,您应该开始看到问题所在:在字面值“锁”上同步的每段代码都共享同一个锁!这很容易导致完全不相关的代码段发生死锁。

您需要注意的不仅仅是String对象。装箱的原语也是一种危险,因为autoboxing和valueOf方法可以重用相同的对象,这取决于值。

有关更多信息,请参阅: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused