JDK1.6的锁优化
曾经遇到多线程加锁操作都是直接用synchronized,但在jdk1.6前,synchronized是重量级锁,随着jdk1.6对synchronized进行的各种优化后,synchronized并不会显得那么重
如:
-
偏向锁:偏向某一个线程的锁,这个线程将对象头markword的ThreadID改为自己的ID,之后再次访问这个对象的时候,只需要对比ID
-
轻量级锁:不需要申请互斥量,只需要将markword中的指针指向线程的id,如果更新成功则表示已经成功的获取了锁,否则说明已经有线程获取了轻量级锁,发生了锁竞争,轻量级锁开始自旋
-
自旋锁:CAS锁
-
适应性自旋锁:CAS自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
-
锁消除:在有些情况下,JVM检测到不可能存在共享数据竞争,这时JVM会对这些同步锁进行锁消除
-
锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,减少连续的加锁、解锁操作
jdk1.6优化了锁机制,锁的状态有了四种(无锁、偏向锁、轻量级锁、重量级锁)
四种状态随着竞争情况升级,且不可逆:无锁-》偏向锁-》轻量级锁-》重量级锁
锁的四种状态
无锁、偏向锁、轻量级锁、重量级锁
结合对象布局:Java - 对象的内存布局(64位HotSpot)
在对象上加锁即会改变对象头MarkWord段
无锁01
无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功
这个时候多个线程竞争资源:有一个线程能竞争成功,而其他竞争失败的线程会不断重试直到竞争成功
在有多线程竞争的情况下是不可取的
偏向锁01
偏向锁:偏向某一个线程的锁,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗
其执行机制:
- 初次执行到synchronized代码块的时候,MarkWord段改变,前54位存储锁偏向的线程ID
- 当第二次到达同步代码块时,线程会判断此时持有锁的线程id是否就是自己,如果是则正常往下执行(没有加锁、解锁开销)
- 遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。线程是不会主动释放偏向锁的,而是等到竞争出现才释放锁
- 偏向锁的释放,需要等待全局安全点,即在某个时间点上没有字节码正在执行时
偏向锁的升级机制:
- 两个以上线程竞争偏向锁,直接升级为轻量锁
- 偏向锁撤销次数达到40次,升级为轻量锁
轻量锁00
当存在竞争时,偏向锁不能满足当前场景,升级为轻量锁,轻量级锁处理竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景
轻量锁: 不需动用操作系统,而是无锁竞争,通过CAS完成资源的竞争
乐观锁的实现 - CAS、ABA问题及Java中原子操作类解析
CAS的缺陷
- ABA问题:CAS算法不知道数据的ABA修改
- CPU开销过大:许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力
synchronized锁是阻塞队列,一个线程占有资源后其他线程直接阻塞;而CAS是所有线程一直在自旋,所以CAS强调多读场景 - 不能保证代码块的原子性:CAS算法是通过比较V与A判断是否被其他线性修改,如果同时需要保证多个变量同时更新(代码块的原子性),CAS无法做到
Java中的自旋锁向重量级锁升级机制
- jdk1.6之前是自旋超过10次,或者等待线程超过cpu核数的2分之一,自动升级
- jdk1.6以后是自适应自旋锁,它会自己判断什么时候升级
重量级锁10
当竞争严重时,轻量级锁并没有带来性能提升,就升级为重量级锁
重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态