Synchronized关键字说明
Java
中的同步代码块用Synchronized
关键字来标记,而该关键字依赖于JVM
来实现,因而我们不用自己去考虑如何去加锁与解锁,加锁与解锁操作由JVM
本身来维护,这就简化了我们对Synchronized
关键字的使用难度。
在Java
中的一个同步代码块有时是同步在不同的对象上的。在相同的对象上,在同一时间内所有的同步代码块中只有一个线程在执行。所有其它的线程都在试图进入同步代码块时被阻塞,直到同步代码快中的线程退出该块代码为止。
Synchronized
属于不可中断锁,它的使用场景是那些适合竞争不太激烈的地方,可读性好。
synchronized
关键字可以用来修饰如下四种不同类型的代码块:
- 实例方法
- 静态方法
- 实例方法中的代码块
- 静态方法中的代码块
这些块在不同的对象上同步。您需要哪种同步块取决于具体的情况。
实例方法
实例方法如下:
public synchronized void add(int value){
this.count += value;
}
注意方法声明中使用了synchronized
关键字。这告诉Java
方法是同步的。
Java
中的同步实例方法在拥有该方法的实例(对象)上同步。因此,每个实例的同步方法在不同的对象上同步:拥有实例。在同步实例方法中只能执行一个线程。如果存在多个实例,则每次可以在每个实例的同步实例方法中执行一个线程。每个实例一个线程。
静态方法
静态方法被标记为synchronized
,就像使用synchronized
关键字的实例方法一样。下面是一个Java
同步静态方法的例子:
public static synchronized void add(int value){
count += value;
}
这里的synchronized
关键字还告诉Java
方法是同步的。
同步静态方法在同步静态方法所属的类的类对象上同步。由于每个类在Java VM
中只存在一个类对象,所以在同一个类的静态同步方法中只能执行一个线程。
如果静态同步方法位于不同的类中,则可以在每个类的静态同步方法中执行一个线程。每个类一个线程,不管它调用哪个静态同步方法。
实例方法中的代码块
您不必同步整个方法。有时,最好只同步方法的一部分。方法内部的Java
同步块使这成为可能。
这是一个非同步Java
方法中的同步Java
代码块:
public void add(int value){
synchronized(this){
this.count += value;
}
}
本例使用Java
同步块构造函数将代码块标记为同步。这段代码现在将像同步方法一样执行。
注意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);
}
}
}
因此,在本例中,只有一个线程可以在两个同步块中执行。
如果在不同的对象上同步了第二个同步块,那么每次就可以在每个方法中执行一个线程。
静态方法中的代码块
下面是与静态方法相同的两个示例。这些方法在方法所属类的类对象上同步:
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
的对象上,然后在每个方法中可以同时执行一个线程。
Synchronized用例
下面的示例启动了两个线程,并让它们在同一个Counter
实例上调用add
方法。一次只能调用同一个实例上的add
方法,因为该方法在它所属的实例上是同步的。
Counter
public class Counter{
long count = 0;
public synchronized void add(long value){
this.count += value;
}
}
CounterThread
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);
}
}
}
Example
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
被装载到两个不同的CounterThread
实例构造函数中。Counter.add()
方法通过synchronized
关键字在实例上同步,因为add
方法是一个实例方法,它通过synchronized
关键字来修饰,因此,每次只有一个线程可以调用add()
方法。另一个线程将等到第一个线程离开add()
方法后才能执行该方法本身。
如果这两个线程引用了两个单独的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()
的调用。