一、synchronized
synchronized用来保证代码的原子性,通常有三种用法:
1.1修饰实例方法
作用于当前对象实例枷锁,进入同步代码前要获得当前对象实例的锁。
synchronized void method() {
//业务代码
}
1.2修饰静态方法
给当前类枷锁,会作用于类的所有对象实例,进入同步代码前要获得当前class的锁,因为静态成员不属于任何一个对象,是类成员(static表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。
如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized修饰的方法占用的是当前类的锁,而访问非静态synchronized方法占用的是当前实例对象的锁。
synchronized void static method(){
//方法体
}
1.3修饰代码块
指定加锁对象,对给定对象/类加锁。
synchronized(this/object)表示进入同步代码库前要获得给定对象的锁。
snychronized(类.class)表示进入同步代码前要获得当前class的锁。
synchronized(this){
//业务代码
}
二、synchronized实现原理
2.1synchronized是怎么加锁的?
使用synchronized不用自己lock和unlock,因为JVM帮我们把这个事情做了。
1、synchronized修饰代码块时,JVM采用monitorenter、moniterexit两个指令来实现同步,monitorenter指令指向同步代码块的开始,monitorexit指令指定同步代码块的结束位置。
反编译一段synchronized修饰的代码块:
javap -c -s -v -l SynchronizedDemo.class
可以看到相应的字节码指令:
2、synchronized修饰同步方法时,JVM采用ACC_SYNCHRONIZED标记符来实现同步,这个标识指明了该方法是一个同步方法。
反编译代码如下:
2.2synchronized锁住的是什么
monitorenter、moniterexit或者ACC_SYNCHRONIZED都是基于Monitor实现的。
实例对象结构里有对象头,对象头里面有一块结构叫Mark Word,Mark Word指针指向了monitor。
所谓的Monitor其实是一种同步工具,也可以说是一种同步机制。在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,可以叫做内部锁,或者Monitor锁。
ObjectMonitor的工作原理:
ObjectMonitor有两个队列:WaitSet、EntryList,用来保存ObjectWaiter对象列表;
_owner,获取Monitor对象的线程进入_owner区时,_count+1。如果线程调用了wait()方法,此时会释放Monitor对象,_owner恢复为空,_count-1。同时该等待线程进入_WaitSet中,等待被唤醒。
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录线程获取锁的次数
_waiters = 0,
_recursions = 0; //锁的重入次数
_object = NULL;
_owner = NULL; // 指向持有ObjectMonitor对象的线程
_WaitSet = NULL; // 处于wait状态的线程,会被加入到
_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入
到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
可以类比一个去医院就诊的例子:
首先,患者在门诊大厅前台或自助挂号机进行挂号;
随后,挂号结束后患者找到对应的诊室就诊:
诊室每次只能有一个患者就诊;
如果此时诊室空闲,直接进入就诊;
如果此时诊室内有其它患者就诊,那么当前患者进入候诊室,等待叫号;
就诊结束后,走出就诊室,候诊室的下一位候诊患者进入就诊室。
这个过程和Monitor机制比较相似:
门诊大厅:所有待进入的线程都必须先在入口Entry Set 挂号才有资格;
就诊室:就诊室_Owner 里里只能有一个线程就诊,就诊完线程就自行离开
候诊室:就诊室繁忙时,进入等待区(Wait Set) ,就诊室空闲的时候就从等
待区(Wait Set) 叫新的线程
那么,同步锁到底锁的是什么呢?
monitorenter,在判断拥有同步标识ACC_SYNCHRONIZED抢先进入此方法的线程会优先拥有Monitor和owner,此时计算器+1。
monitorexit,当执行完退出后,计数器-1,归0后被其他进入的线程获得。