1.synchronized的应用
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchonized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
1.1对于普通同步方法,锁是当前实例对象
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
synchronizedDemo.methodA(); //锁是synchronizedDemo对象
synchronizedDemo.methodB(); //锁是synchronizedDemo对象
SynchronizedDemo synchronizedDemo1=new SynchronizedDemo();
synchronizedDemo1.methodA(); //锁是synchronizedDemo1对象
synchronizedDemo1.methodB(); //锁是synchronizedDemo1对象
}
public synchronized void methodA(){
}
public synchronized void methodB(){
}
}
1.2对于静态同步方法,锁是当前类的Class对象
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo.methodC(); //锁是当前的Class对象
SynchronizedDemo.methodD(); //锁是当前的Class对象
}
public static synchronized void methodC(){
}
public static synchronized void methodD(){
}
}
1.3 对于同步方法块,锁是Synchonized括号里配置的对象
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
//锁是Synchonized括号里配置的对象
synchronized (synchronizedDemo){
}
}
}
2.synchronized的实现原理
在如下的例子中,使用了同步块和同步方法,通过使用javap工具查看生成的class文件信息来分析synchronized关键字的实现细节
public class Synchronized {
public static void main(String[] args){
// 对Synchronized Class对象进行加锁
synchronized (Synchronized.class){
}
// 静态同步方法,对Synchronized Class对象进行加锁
m();
}
public static synchronized void m(){
}
}
在Synchronized.class同级目录执行javap–v Synchronized.class,部分相关输出如下所示:
public static void main(java.lang.String[]);
// 方法修饰符,表示:public staticflags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #1 // class com/murdock/books/multithread/book/Synchronized
2: dup
3: monitorenter // monitorenter:监视器进入,获取锁
4: monitorexit // monitorexit:监视器退出,释放锁
5: invokestatic #16 // Method m:()V
8: return
public static synchronized void m();
// 方法修饰符,表示: public static synchronized
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
由上面的信息可知:
- 同步块的实现原理:同步块的实现使用了monitorenter和monitorexit指令,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
- 同步方法的实现原理:同步方法依靠方法修饰符上的ACC_SYNCHRONIZED来完成的
- 无论采用哪种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用
时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。
图4-2描述了对象、对象的监视器、同步队列和执行线程之间的关系。
从图4-2中可以看到,任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。
当获得了锁的线程释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。