锁时什么?
锁是访问资源的凭证,它是为了保证所对象的串行访问和安全性。
对象头mark是什么?
描述对象的hash、锁信息、垃圾回收标记、gc年龄等信息
-执行锁记录的指针
-执行monitor的指针
-GC标记
-偏向锁线程id
锁类型
偏向锁
锁偏向于当前已经占有锁的线程
将对象头mark的标记设置为偏向,并将线程id写入对象头mark
在没有竞争的条件下,获得偏向锁的线程,进入同步快,不需要做同步
当其他线程请求相同的锁时,偏向模式结束
在竞争不是很激烈的情况下,可以提高性能;但是在激烈的竞争下,偏向锁反而会增加系统负担
轻量级锁
BasicObjectLock嵌入在线程栈中的对象
快速的锁定方法
如果对象没有被锁定,将对象头的mark指针保存到锁对象,将对象头设置为指向锁的指针,锁对象BasicObjectLock位于线程栈中
如果轻量级锁失败,表示存在竞争,升级为重量级锁。
自旋锁
当存在竞争时,如果线程可以很快获得锁,可以让线程做几个空操作(自旋)
如果同步快很短,自旋成功,节省线程挂起切换时间,提高系统性能;如果同步快很长,自旋失败,还是会线程挂起,会降低系统性能。
获取锁步骤:首先会尝试获得偏向锁,获取失败会尝试获取轻量级锁,获取失败会获取自旋锁,再失败就获取重量级锁,在操作系统层挂起
锁优化手段
减少锁持有时间
减小锁粒度
锁分离 比如说读写锁、LinkedBlockingQueue(take锁、put锁)
锁粗化
锁消除
无锁(乐观锁)
乐观锁是通过CAS(compare and swap)来实现非阻塞判断。
CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。
CAS原理:CAS通过调用JNI(java native interface)的代码实现的。而compareAndSwapInt就是借助C来调用CPU底层指令实现的。
CAS缺点:
- ABA问题
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
解决方案:在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A - 循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的开销。自旋时会占用cpu时间片,会造成cpu的浪费。同时还会因为偏向锁的原因,可能会出现线程饥饿
解决方案:引入自适应自旋,而不是一个固定的自旋时间。 - 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
解决方案:从Jdk1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作
CAS与Synchronized的使用情景
- 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
- 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。