上篇文章和大家聊了聊hashmap和concurrenthashmap的结构、用法、原理,从这篇文章开始次我们来聊聊并发编程吧!本次我将带大家了解一下synchronized的原理。
synchronized从1.6优化了之后并不是上来就很重,而是有了多个锁状态,分别是偏向锁、轻量级锁、重量级锁。
1)「重量级锁」
synchronized是依赖jvm实现同步的,他在同步代码块里和同步方法的原理有一些区别: 1、同步代码块:通过monitorenter和monitorexit指令实现的,每个对象都有一个监视器锁monitor,monitor被占用的时候就说明这个对象处于一个锁定状态,而monitorenter指令的作用就是去获取这个monitor的所有权,monitorexit指令就是去释放monitor。我来说一下他们的机制: monitorenter:每个monitor有个进入数,当它为0的时候表示没有线程在占用,当前线程就可以进入monitor,并将进入数设置为1;那如果当前线程已经拥有这个monitor,又重新进入的话,进入数就会+1,这就是锁的重入;如果monitor已经被其他线程占有,那么当前线程进入阻塞状态,等待monitor被释放,再去尝试获取。 monitorexit:首先必须要拥有这个monitor才能去执行monitorexit,执行后进入数-1,如果到0了就退出这个monitor,不再拥有。其他线程可以尝试去获取。 2、同步方法:方法常量池中会多一个ACC_SYNCHRONIZED标识,调用方法的调用指令会让线程去获取monitor,获取成功的话再继续执行方法,方法执行完毕后再释放monitor,同一个monitor同一时刻只能被一个线程拥有。 「总结」:二者都需要获取和释放monitor来实现线程同步,monitor的实现依赖于操作系统的mutex互斥锁,操作系统的线程之间的切换需要从用户态切换到内核态,开销比较大。
「2)轻量级锁」
相对于使用mutex的重量级锁来说的,他的实现主要是基于对象头的mark Word,线程进入同步方法或者同步代码块的时候,如果同步对象处于无锁状态(锁标志位为"01"状态,是否为偏向锁为"0),那么虚拟机会在栈帧里建立一个lock record(锁记录),获取锁的时候会把对象头的mark Word给拷贝过来,然后通过CAS操作把mark Word 更新为指向lock record的指针,更新成功后就拥有了该对象的锁,并把锁标志位改为00(轻量级锁)。 更新失败会检查mark Word是否是指向当前线程的,是的话表示当前线程已经有了这个对象的锁,然后进入代码块里执行。否则的话就表示已经被其他线程抢占了,然后就进入一个自旋,再次尝试cas更新指针。如果自旋结束还是没获取锁,那就膨胀为重量级锁,锁标志位状态值变为"10",Mark Word中储存就是指向
monitor
对象的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。 释放锁就是通过cas将lock record里拷贝的markWord给替换回去,替换成功进入无锁状态;失败说明有其他线程尝试获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。
「3)偏向锁」
如果说轻量级锁是在无竞争的情况下使用
CAS
操作区消除同步使用的互斥量,那么偏向锁就是把整个同步都消除掉. 检查Mark Word是否为可偏向锁的状态,即是否偏向锁即为1即表示支持可偏向锁,否则为0表示不支持可偏向锁。如果是可偏向锁,则检查Mark Word储存的线程ID是否为当前线程ID,如果是则执行同步块。如果检查到Mark Word的ID不是本线程的ID,则通过CAS操作去修改线程ID修改成本线程的ID,如果修改成功则执行同步代码块,否则执行步骤4。当拥有该锁的线程到达安全点之后,挂起这个线程,升级为轻量级锁。 释放: 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。然后等待全局安全点(在这个是时间点上没有字节码正在执行)。暂停拥有偏向锁的线程,检查持有偏向锁的线程是否活着,如果不处于活动状态,则将对象头设置为无锁状态,否则设置为被锁定状态。如果锁对象处于无锁状态,则恢复到无锁状态(01),以允许其他线程竞争,如果锁对象处于锁定状态,则挂起持有偏向锁的线程,并将对象头Mark Word的锁记录指针改成当前线程的锁记录,锁升级为轻量级锁状态(00)。
比较