synchronized
synchronized具体实现,这个是在JVM内部完成的
- 开始使用的时候是乐观锁,如果发现锁的冲突率比较高,就会自动转换为乐观锁
- synchronized不是读写锁
- synchronized开始的时候是轻量级锁,如果锁被持有的时间较长/锁的冲突概率较高,就会升级成重量级锁
- synchronized是一个非公平锁
- synchronized是一个可重入锁
- synchronized为轻量级锁的时候,大概率是一个自旋锁;为重量级锁的时候大概率是一个挂起等待锁
synchronized的锁升级
偏向锁
偏向锁,其实也就是一种乐观锁。
类似于 “我赌你的枪里没有子弹”,偏向锁就是在赌,这个锁不会产生竞争。
偏向锁只是在对象头中设置了一个“偏向锁标记”,这个只做标记,就比真正的加锁,要高效很多。
如果赌赢了,那么这次就真不加锁了,直到锁的释放,这整个过程中根本没有涉及到加锁解锁,因此就非常快
如果赌输了,比如线程1 尝试获取这把锁,锁进入偏向锁状态,此时如果有一个线程2,也尝试竞争这个锁,那么此时线程1就会抢先先把这个锁拿到,然后线程2 去等待。
偏向锁本质上相当一一种"延时加锁"
完全没有竞争的时候,是偏向锁
如果出现了竞争,但是这时候竞争还比较小,此时我们就会进入到“轻量级锁”状态
此处的轻量级锁,就是基于CAS实现的自旋锁,是属于完全在用户态完成的操作
因此这里不涉及到内核态用户态的切换,也不涉及到线程的阻塞等待和调度,只是多浪费了一些CPU而已
但是如果当前的场景,是锁的冲突比较大,锁的竞争比较激烈。
此时锁还会进一步的膨胀成重量级锁
如果锁冲突率太大了,轻量级自旋锁,就会浪费大量的CPU(在等待的时候CPU是空转的)
使用更重量的挂起等待锁,就可以解决这个问题。
对于挂起等待锁来说,当锁等待的过程中,是释放CPU(不会占用CPU资源)
代价就是引入了线程的阻塞和调度开销
锁的升级(偏向锁 ——> 轻量级锁 ——> 重量级锁)
锁消除
官方的JDK1.8中是没有实现锁降级的,但是synchronized有一种优化手段,锁消除
锁消除,其实就是编译器和JVM自行判定一下,看着当前这个代码是否真的需要加锁。JVM和编译器,会对代码中的synchronized进行简单的判断,如果这个加锁没有必要。如果每必要就算程序员加了锁,编译器也会自动的把锁给去掉。但这个优化不能保证每个没必要加锁的地方,都被优化掉。
锁的粗化
锁的粒度,就是看synchronized代码块中包含了多少代码
如果包含的代码多,认为锁的粒度比较粗。
如果包含的代码少,认为锁的粒度比较细。
一般情况下还是把加锁的代码写细一点比较好,比如我们一把锁需要保护多个相互独立的操作,那么我们就可以将这个锁分解成多个锁。如果锁的粒度细,意味着代码持有锁的时间就短,就能更快释放,这样其它线程冲突的概率就更低。
但是有的情况还是写粗一点更好
public void func() {
synchronized (this) {
//任务1
}
synchronized (this) {
//任务2
}
synchronized (this) {
//任务3
}
}
这种写法还不如
public void func() {
synchronized (this) {
//任务1
//任务2
//任务3
}
}
就算程序员不这么写,JVM和编译器也会进行只能判定,会把这个情况的多个synchronized合并成一组