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()方法的调用。