什么是偏向级锁、轻量锁、重量级锁
首先,我们需要明确一点:这三种锁只针对synchronized
我们都知道,任意一个java对象都可以做为锁,java对象的锁信息存储在对象头中的Mark Word字段中。Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下图:
Java中锁主要存在四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,随着竞争的激烈而逐渐升级。锁只能升级而不能降级,即一个锁从偏向级锁升级到轻量锁时,不能再重新回到偏向级锁。
偏向级锁
顾名思义,偏向某一个线程,当线程数目不多的时候,由于反复获取锁会使得我们的运行效率下降,于是出现了偏向级锁。JVM使用CAS操作把线程ID记录到对象的Mark Word当中,并修改标识位,name当前线程就拥有了这把锁。
偏向级锁不需要操作系统的介入,JVM使用CAS操作将线程ID放入对象的Mark Word字段中,于是线程获得了锁,可以执行synchronized代码块的内容,当线程再次执行到这个synchronized的时候,JVM通过锁对象的Mark Word判断 :当前线程ID还存在,还持有这个对象的锁,于是就可以继续进入临界区执行,而不需要再次获得锁
偏向锁,在没有别的线程竞争的时候,一直偏向当前线程,当前线程就可以一直进入synchronized修饰的代码块一直运行。
如果在运行中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁升级到轻量级锁。
总结一点:偏向级锁就是为了消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。
轻量级锁
轻量级锁是由偏向级锁升级来的,当一个线程运行同步代码块时,另一个线程也加入想要运行这个同步代码块时,偏向锁就会升级为轻量级锁。
首先,JVM会将锁对象的Mark Word恢复成为无锁状态,在当前两线程的栈桢中各自分配一个空间,叫做Lock Record,把锁对象account的Mark Word在两线程的栈桢中各自复制了一份,官方称为:Displaced Mark Word
然后一个线程尝试使用CAS将对象头中的Mak Word替换为指向锁记录的指针,如果替换成功,则当前线程获得锁,如果失败,则当前线程自旋重新尝试获取锁。当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两个或两个以上线程竞争同一个锁),则轻量级锁会膨胀成重量级锁
举个例子: 线程A、线程B同时想要执行一个同步代码块,假设线程A抢到了锁,则线程A的Lock Record的地址 会被CAS操作放到了锁对象Mark Word中,并且将锁标志位改为00,这意味着线程A就获取到了该锁,可以执行同步代码块。
而线程B没有抢到锁,但是线程B不会阻塞,而是通过自旋的方式,等待获取锁。(一般默认自旋10次),如果线程A释放掉锁,则将线程A中的Displaced mark word使用CAS复制回锁对象的Mark Word字段,此时线程B就可以获取锁对象,如果线程B还没有获取成功,则说明同时存在两个或两个以上的线程同时竞争这一把锁,轻量级锁会升级成为重量级锁。
重量级锁
我们上面提到,当多个线程竞争同一个锁时,会导致除锁的拥有者外,其余线程都会自旋,这将导致自旋次数过多,cpu效率下降,所以会将锁升级为重量级锁。
重量级锁需要操作系统的介入,依赖操作系统底层的Muptex Lock。JVM会创建一个monitor对象,把这个对象的地址更新到Mark Word中。
当一个线程获取了该锁后,其余线程想要获取锁,必须等到这个线程释放锁后才可能获取到,没有获取到锁的线程,就进入了阻塞状态。