并发专题
目录
synchronized原理详解
synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的
什么是Monitor
在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的。Synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。其主要数据结构如下
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:
- 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
- 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒;
- 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其他线程进入获取monitor(锁);
synchronized底层原理
synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现。JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销。
每个同步对象都有一个自己的Monitor(监视器锁):
synchronized锁的升级过程
public class Test04 {
private static Object objectLock = new Object();
public static void main(String[] args) throws InterruptedException {
//-XX:BiasedLockingStartupDelay=0 强制开启
// System.out.println(">>----------------无锁状态001-------------------<<");
System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());
System.out.println("开启了偏向锁,但是偏向锁没有关联偏向锁线程");
synchronized(objectLock){
// 偏向锁 关联偏向锁线程
System.out.println("开启了偏向锁,偏向是给我们的主线程");
System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());
}
// 撤销偏向锁 是另外一个线程与偏向锁线程竞争
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objectLock) {
try {
System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());
Thread.sleep(5000);
System.out.println("子线程:升级为轻量级锁");
} catch (Exception e) {
}
}
}
}, "子线程1").start();
Thread.sleep(1000);
sync();
}
public static void sync() throws InterruptedException {
System.out.println(" 主线程获取锁 重量级别锁");
//11010000 01000000
synchronized (objectLock) {
System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());
}
}
}
分析synchronized偏向锁原理
1.当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出;
2.同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则,使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
synchronized撤销原理
偏向锁的撤销需要等待全局的安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁状态,撤销偏向锁后恢复到未锁定(标志位001)或者升级轻量级锁(标志位00)的状态
synchronized轻量级锁原理
synchronized轻量锁获取锁实现原理
1. 创建锁记录(Lock Record)对象,每个线程的栈帧(方法)都会包含一个锁记录的结构,内部可以储存锁定关联对象的Mark Word
2. 锁记录中Object reference (对象引用)指向锁对象,采用CAS算法 替换Object锁对象 的Mark Word,将Mark Word 的值存入锁记录
3. 如果CAS执行成功,则对象头中存储了锁记录地址和状态00,表示该线程获取到锁