synchronized
Java语言的关键字,用来保证多线程场景中的,同一时刻只能有一个进程访问本方法或代码块(通过加java内置的锁),保证并发场景下的共享资源的操作同步。
一,使用方法:
修饰普通方法和普通代码块时(非静态),是一个对象实例的方法和代码块加锁(需要注意的是,因为锁是针对对象的,如果该对象的类中有多个方法加了synchronized,那么这些方法将被正在访问的线程同时锁住,其他线程不能访问其他synchronized方法)。
修饰静态方法和静态代码块时,就是给一个类的所有对象对该方法/代码块的调用。
二,synchronized的实现原理:
2.1synchronized代码块的同步实现
是通过monitorenter 和 monitorexit 指令来完成的,如下代码所示,在加了synchronized关键字后,jvm会在编译后在同步的代码块前后分别加入monitorenter 和 monitorexit 。
public class TestSync {
public static void main(String[] args) {
synchronized (TestSync.class){
System.out.println("synchronize");
}
}
}
如下图所示,为对应的代码块字节码,前后加了monitorenter 和 monitorexit;
monitorenter 和 monitorexit
2.2而synchronized修饰方法则稍有区别:
如下所示:
public class TestSyncMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
对应的指令码如下,说明方法是直接在方法写synchronized关键字的,但处理过程与代码块类似:
synchronized方法
三,monitorenter和monitorexit的实现:
那么,monitorenter和monitorexit到底如何实现让多个线程的同步的呢。
3.1 首先,需要再了解下之前科普过的关于java对象头的信息(以32位jvm为例):
如下图所示,对象新建出来之后,默认“无锁”状态,而使用“偏向锁”和“轻量级锁”后续章节中讨论,本文默认synchronized走的是“重量级锁”(java6之前的实现)。
java对象头信息,摘自
“重量级锁”这一行,指向的指针就是指向monitor对象(重量级锁)。
且如果对象被某个线程占有,锁标志更新为"10"。
3.2 Monitor对象的实现方式(HotSpot虚拟机源码ObjectMonitor.hpp文件):
ObjectMonitor() {
_count = 0; //重入次数
_object = NULL;//对应的对象
_owner = NULL;//持有者(某个持有该锁的线程)
_WaitSet = NULL; //等待队列(线程等待唤醒的队列)
_EntryList = NULL ; //同步队列(多个线程进入后尝试获取锁的队列)
.....//其他属性
}
3.3 执行过程
如下图所示,当多个线程进入monitorenter时,先进入_EntryList 同步队列,尝试获取锁,如果获取失败,则在同步队列等待占有锁的线程先释放锁,如果获取成功,则_owner值指向该线程。如果该线程再次进入monitorenter,则_count+1。如果该线程占有过程中,执行了wait方法,则线程进入_WaitSet,并释放_owner值,_count为0,由其他线程抢占该monitor对象。抢占到monitor的线程执行完同步代码后,执行monitorexit,则并释放_owner值,_count为0。
线程抢占锁的原理
后面,我们还将继续在“锁”这一章节中讲述java6之后对锁的优化。
感兴趣的小伙伴请关注本人公众号:暖爸的java家园