原文链接:http://tutorials.jenkov.com/java-concurrency/synchronized.html
原作者: Jakob Jenkov
翻译者:呼啸
一个java同步块标记着一个代码块或者方法是同步的。Java同步块可以在避免竞争条件时使用。
这里是一个在本教程中所涵盖的主题的列表:
*java 同步关键字
*同步实例方法
*同步静态方法
*在实例方法中的同步锁
*在静态方法中的同步锁
*java同步例子
Java同步关键字
在java中同步锁是用” synchronized”关键字标记的。在java中一个同步块是同步在一个对象上。在一个对象中同一时间只能有一个线程执行在这个对象里面的同步块。在此同步块中的这个线程退出这个同步快之前,所有其他的线程试图执行这个同步块都将被阻塞。
” synchronized”关键字可以在用来标记四种不同类型锁时使用:
1、 实例方法
2、 静态方法
3、 在实例方法中的代码块
4、 在静态方法中的代码块
这些同步快在不同的对象中同步。你需要使用哪种锁需根据具体的情况。
同步实例方法
这里有一个同步实例方法:
public synchronized void add(int value){
this.count += value;
}
注意在方法生明中synchronized关键字的使用。他告诉java这个方法是同步的。
Java中的同步实例方法是同步在拥有该方法的实例(对象)上。因此,当同步在拥有该实例的不同的对象上时每个实例有他自己的同步方法。在一个同步实例方法中仅有一个线程可以执行。如果有多个实例存在,在每个实例的其中一个同步实例方法中,一次只能有一个线程执行。一个实例一个线程。
同步静态方法
静态方法被标记为同步的就像实例方法一样使用synchronized关键字。这里有一个java同步静态方法的例子:
public static synchronized void add(int value){
count += value;
}
这里这个synchronized关键字也是告诉java这个方法是同步的。同步静态方法在这个同步静态方法所属的类的类对象上是同步的。由于在JAVA 虚拟机中每个类只有一个类对象,在同一个类中的一个静态同步方法中只有一个线程可以执行。如果这个静态同步方法被放置于不同的类中,在每个类中的静态同步方法中,只能有一个线程执行。一个类一个线程,无论他调用的是哪个静态同步方法。
在实例方法中的同步块
你不需要去同步整个方法,有时候仅仅同步方法中的一部分更好,在方法中的java同步快使这个变成了可能。
未同步的方法中的java代码同步块:
public void addA(int value){
synchronized (this) {
this.count2+=value;
}
}
这个例子使用Java synchoronized块构造去标记一个代码块为同步的。这个代码执行起来仿佛他本身是一个同步方法一样。
注意这个java同步块是如何在括号中带一个对象的。在例子中,”this”被使用,他指的是调用add方法的实例。这个对象被同步构造器带进了括号中,称之为监听对象。该代码在所述监听对象上同步。一个同步实例方法使用这个对象作为一个监听对象。
在同一个监听对象上,同步在该对象上的一个java代码块中只能有一个线程可以执行。
下面这两个例子都同步在调用他们的实例上,因此他们相当于同步:
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);
}//译者注:此处this作为监听对象,this指的是调用这个方法的实例。这个方法将同步在这个实例上。
}
}
因此在这个例子中的两个同步块中只能有一个线程可以执行。
如果第二个同步块被同步在了不同于this的对象中,那么在每个方法中一个线程可以在同一时间执行。
在静态方法中的同步块
这里同样有连个静态方法例子。这些方法同步在了方法所属的类的类对象上:
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同步例子
这里有个例子,他启动了2个线程并且都调用位于同一个Counter实例上的add方法。在同一时刻,只能有一个线程能调用add方法。因为这个方法同步在他所属于的实例上。
public class Counter {
long count = 0;
public synchronized void add(long value){
this.count += value;
}
}
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);
}
}
}
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();
}
}
两个线程被创建。同一个Counter实例在这两个线程的构造器中被传递过去。Counter.add()方法同步在了这个实例上。因为这个add方法是实例方法,而且被标记为同步的。因此在同一时刻只能有一个线程可以调用这个add方法。在其他的线程能自己执行这个方法之前,其他的线程将等待直到第一个线程离开add方法。如果这两个线程指向两个分离的Counter实例。
如果这两个线程指向连个分离的Counter实例,那他们在同一时刻调用add方法是没有问题的。因为调用的是不同的对象,所以被调用的方法也同步在了不同的对象上(方法所属的对象)。因此调用不会被锁上。请看下面:
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();
}
}
注意threadA和threadB这两个线程不再引用同一个counter实例,counterA和counterB的add方法被同步在了他们两个的实例上。调用counterA上的add方法不会影响调用counterB上的add方法。