synchronized block(下面称之为同步块)可以将一个方法或者一个代码块标记为synchronized。synchronized block可以用于避免竞争条件(race condition,具体可参考Race Conditions and Critical Sections)。

The Java synchronized Keyword

同步块同一时刻也只能被一个线程调用执行,其他试图进入此同步块的线程将被阻塞,直到当前执行的线程(即拥有锁的线程)退出。

        synchronized关键字可用来标记四种类型的同步块:

        实例方法;静态方法;实例方法中的代码块;静态方法中的代码块;

        这些块分别基于不同的对象实现了同步保证,具体需要哪种视情况而定。

Synchronized Instance Methods

        下面是一个同步的实例方法:

public synchronized void add(int value){
      this.count += value;
}

        请注意synchronized关键字在方法声明中的使用,这告诉java编译器此方法是同步方法。

拥有此方法的对象实现的同步(即需要获得该方法所属对象的锁)。因此,同一时刻只有一个线程可以运行该方法。如果存在多个实例对象,那么一个线程可执行一个对象的同步方法,也即一个线程对应一个对象(这种情况下,每个线程获取一个对象的锁,彼此之间不存在竞争,所以是线程安全的)。

Synchronized Static Methods

        如同普通的类方法被标记为同步方法一样,静态方法也是通过在方法声明部分增加synchronized关键字实现的。下面是一个静态同步方法的例子:

public static synchronized void add(int value){
      count += value;
}

        静态同步方法是基于

该静态方法所属的类的类对象实现的同步 (即需要获得该方法所属类的锁,类也有锁)。因为JVM中每个类只有一个类对象(也就是说一个类只有一个锁),所以同一时刻只能有一个线程执行某一个类的静态同步方法。

        如果多个静态同步方法位于不用的类中,那么多个线程可以分别执行不同类的静态方法,也就是一个线程对应一个类(

此刻,他们分别获得对应类的锁,彼此之间不存在竞争 )。

Synchronized Blocks in Instance Methods

有时候,不必对整个方法进行同步(性能考虑),只需对部分代码块同步即可。Java提供了基于方法内部的代码块同步机制。

下面是一个在java非同步方法里面的同步代码块的示例:

public void add(int value){

    synchronized(this){
       this.count += value;   
    }
}

        这个例子就使用了synchronized关键字将一个方法内部的普通代码块标记为同步代码块,从此该代码块的执行机制等同于同步方法。

        请注意构造同步代码块时括号内有一个参数,上面的例子中使用了“this”关键字,代表调用该add方法的对象。括号内的对象被称为monitor object(别人翻译为管程对象)。而同步代码块就是基于

该monitor object实现同步的(即,需要获得monitor object的锁)。同理,同步实例方法将他所属的对象作为其monitor object。

        同一时刻,只有一个线程可以执行该同步代码块,因为只有一个线程可以获得该monitor object。

        下面的两个例子都是基于他们所属的对象实现的同步,他们是等价的:

public class MyClass {
  
    public synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  
    public void log2(String msg1, String msg2){
       synchronized(this){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
}

Thus only a single thread can execute inside either of the two synchronized blocks in this example.

Had the second synchronized block been synchronized on a different object than this, then one thread at a time had been able to execute inside each method.

显然,同一时刻,只能有一个线程执行该例子中的任意一个方法。

假如log2方法中的同步代码块基于另一个对象而不是“this”实现的同步,那么同一时刻,一个线程就可以同时调用这两个方法(该线程拥有两个对象的锁)。

        PS:以上是原文中的描述以及我的翻译,但是我觉得第一点不正确,java中的synchronized是可重入的,所以例子中,同步方法和同步代码块都是基于当前对象实现的同步,所以一个线程,只要获得了对象的锁,那么他就可以同时调用这两个方法。

Synchronized Blocks in Static Methods

下面是两个静态方法的例子,他们是基于该方法所属类的类对象实现的同步,即需要获得类的锁。

public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
}

同一时刻,只能有一个线程执行其中的一个方法。(不知道类的锁是否可重入)

假设:第二个方法中的同步代码块基于另一个对象而非 MyClass.class实现同步,那么一个线程就有可能同时执行这两个方法了。

Java Synchronized Example

    下面的例子中,有两个线程,他们调用同一个实例对象Counter的add方法。同一时刻,只有一个线程可以成功调用add方法,因为该方法是同步方法,线程必须获得其所属对象的锁才能执行。

<span style="font-size:14px;">public class Counter{
     
     long count = 0;
    
     public synchronized void add(long value){
       this.count += value;
     }
}</span>

<span style="font-size:14px;">public class CounterThread extends Thread{

     protected Counter counter = null;

     public CounterThread(Counter counter){
        this.counter = counter;
     }

     public void run() {
	for(int i=0; i<10; i++){
           counter.add(i);
        }
     }
}</span>

<span style="font-size:14px;">public class Example {

    public static void main(String[] args){
      Counter counter = new Counter();
      Thread  threadA = new CounterThread(counter);
      Thread  threadB = new CounterThread(counter);

      threadA.start();
      threadB.start(); 
    }
}</span>

counter实现了同步。所以,同一时刻,只能有一个线程调用该同步方法,其他线程被阻塞,进行等待。

    如果这两个线程引用不同的Counter类实例,即分别获得一个锁,那么就可以同时调用每个对象的add方法,不存在同步。下面是修改后的示例:

<span style="font-size:14px;">public class Example {

    public static void main(String[] args){
      Counter counterA = new Counter();
      Counter counterB = new Counter();
      Thread  threadA = new CounterThread(counterA);
      Thread  threadB = new CounterThread(counterB);

      threadA.start();
      threadB.start(); 
    }
}</span>

counterA的add()方法并不会阻塞另一个线程对counterB的add()方法的调用。