前言
java提供两种锁:lock
和synchronized
,关于二者的区别,网上一个说法非常好:
synchronized
相当于自动挡汽车,使用简单,可以覆盖大部分使用场景
但如果你想玩漂移等特殊操作,就需要lock
,使用相对麻烦,但可以实现一些特殊场景,如公平锁
实际上二者出身就有本质区别,synchronized
是官方的,而lock
是民间的李二狗写出来的,起初刚开始lock
无论在功能上还是性能上都超越了synchronized
,可以说狠狠的打了官方的脸。
因此在1.5版本之后官方通过锁升级对synchronized
的性能做了提升,本文主要简单介绍synchronized
锁升级的过程
synchronized
synchronized
是一种对象锁(锁的是对象而非引用),作用粒度是对象,java中每个对象都可以上锁(同一时间只有一个线程能上锁成功),而且通过对象内部存储的markword
标记锁状态。
synchronized
加锁方式
1、同步实例方法,锁是当前实例对象
2、同步类方法,锁是当前类对象
3、同步代码块,锁是括号里面的对象
public class Syc {
Object lock = new Object();
public synchronized static void go() { // 锁的是Syc.class对象
}
public synchronized void say() { // 锁的是Syc对象实例
synchronized (lock) { // 锁的是lock对象实例
}
}
}
锁升级
首先过一下synchronized
锁升级的过程
1.偏向锁
当只有一个线程获得了锁,锁就进入偏向模式,MarkWord
标识偏向状态,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作。
2.轻量级锁
当有其它线程要获取锁,竞争不是很激烈,锁进入轻量级锁,MarkWord
标识轻量级状态,此时等待锁的线程开始自旋,即空循环等待锁释放,此过程不释放cpu。
3.重量级锁
当获取锁的竞争变的激烈,比如来了很多个线程或者某个线程自旋等待的次数太多了,锁进入重量级锁,MarkWord
标识重量级状态,重量级锁依赖操作系统的Mutex lock
实现,此时等待锁的线程挂起,当锁释放后再由操作系统唤醒重新尝试获取锁,由于借助操作系统,导致用户态内核态切换,此过程时间成本比较高。
原始的synchronized
是直接使用重量级锁,才会导致性能很低,加入锁升级才使得synchronized
性能获得很大提升。
理解
以上讲解了synchronized
锁升级的过程,如果不好理解,还是拿现实生活举个例子:
假设某公司有多个
会议室
,每个团队
需要获取到会议室的锁
才能进去开会,会议室门口挂着一个写字板
,时刻记录当前会议室使用状态。
-
会议室
相当于对象 -
团队
相当于线程 -
会议室的锁
相当于对象的锁 -
写字板
相当于MarkWord
1.偏向锁
公司发现大部分时间,同一个会议室都是同一个团队占用,于是当A团队第一次占用会议室时,在写字板上写上
偏向 A团队
,下次A团队进入不用修改就可以直接进入会议室,大大提升了开会效率。
如果B团队想使用会议室,此时A团队已经不使用该会议室,则修改写字板偏向 B团队
。
这就是偏向锁,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁带来的开销,所以引入偏向锁
。
2.轻量级锁
如果B团队想使用会议室,此时A还占用着会议室(写字板上记录
偏向 A团队
),此时出现了竞争,写字板上修改为轻量竞争
,B团队哪也不去,就在会议室外原地打转(自旋)等着,因为公司大部分会议时间都很短,B相信A一般会很快出来。
如果A确实一会就出来了,B马上去抢会议室的锁。
这就是轻量级锁,偏向锁出现了竞争会升级为轻量级锁
,因为大部分线程占用锁的时间不会特别长,所以等待线程刚开始不需要挂起,只需要通过空转自旋等待,一般很快就会获取到锁,比过程一直占用着cpu。
3.重量级锁
上面的情况,如果B等了很久A都不出来,或者这段时间公司特别繁忙,各团队频繁开会,还有C,D,E...等等团队也要使用该会议室,这时如果A在里面开会没完没了,其它团队一直在外面傻转着也不是事。
这时候就要请会议室管理员
帮忙了,他让各团队都回去睡觉吧,写字板上修改为重量竞争
,等A团队开完会出来,我负责通知其它团队,你们再过来抢会议室的锁。
这样在会议室竞争特别激烈时,请会议室管理员
帮忙有效的避免了等待团队傻等,但如果在竞争不激烈的情况下就没有必要请出会议室管理员
,毕竟造成额外开销,而且靠会议室管理员
通知再来抢会议室肯定比站会议室外面等要慢很多。
这就是重量级锁,其中会议室管理员
相当于操作系统,当某个线程自旋次数过多或者多个线程同时竞争锁,锁竞争变的激烈,轻量级锁升级为重量级锁
,此时等待线程都挂起,对象锁释放后再由操作系统唤醒线程,此过程开销很大。
synchronized
最开始就是不管竞争激不激烈都使用重量级锁导致性能很低,但竞争激励时如果任由等待线程空转消耗跟大,所以竞争激励升级为重量级锁也是非常合理。
其它
再补充几个问题
可重入锁
synchronized
是一种可重入锁,比如有两个方法A,B锁的都是同一个对象,其中A调用B,那么某线程获取锁后进入A方法也能顺利进入B方法,即自己不会锁自己(否则synchronized
修饰的方法都不能递归了),常用的ReentrantLock
也是可重入锁。
公平/非公平
从上面的描述也会发现,当某个线程释放锁,其它线程会重新竞争锁,没有先来后到,就跟抢公交一样一拥而上,这就是不公平
,我们的 synchronized就是一个非公平锁。
如果想实现公平锁,可以用ReentrantLock
,他会维护一个队列,先到先得,就像排队上地铁,文明多了。
over~
网友评论