Java中的锁机制
Java中多线程加锁机制主要有两种:一种是jdk实现的Lock锁,一种是JVM底层实现的synchronized关键字。
区别:
- synchronized用于方法或代码块;Lock是一个接口,实现类有ReentrantLock可重入锁、ReadWriteLock读写锁。
- synchronized是隐式锁,无需手动开启和关闭,因此不会出现死锁;Lock锁是显示锁,需要手动开启和关闭,如果开启和关闭次数不一致,可能发生死锁。
在JDK1.6后,对synchronized进行了很多优化,如偏向锁、轻量级锁等,synchronized的性能已经与Reentrantlock大致相同,除非要使用Reentrantlock的一些高级功能(实现公平锁、中断锁等),一般推荐使用synchronized关键字来实现加锁机制。
synchronized锁机制
在对象的MarkWord中记录一个锁状态,锁的状态有四种:无锁、偏向锁、轻量级锁、重量级锁。
随着竞争的增加,锁的使用情况如下:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
其中偏向锁和轻量级锁是从 JDK 6 时引入的,在 JDK 6 中默认开启。 锁的升级(锁膨胀)是单向的,只能从低到高(从左到右)。不会出现锁的降级。
锁状态转换
①偏向锁
当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01” (可偏向),即偏向模式,在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
②轻量级锁
当两个线程交替获取一个对象的锁时,偏向锁会升级为轻量级锁。这个阶段线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,则自旋获取锁,当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两条或两条以上的线程竞争同一个锁),则轻量级锁会膨胀成重量级锁。
自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。但是线程自旋是需要消耗CPU的。所以自旋的次数一般控制在一个范围内,例如10,100等,在超出这个范围后,自旋锁会升级为阻塞锁。
③重量级锁
通过系统的线程互斥锁来实现的,代价最昂贵。