synchronized
ynchronized是一种对象锁(锁的是对象而非引用),作用粒度是对象,java中每个对象都可以上锁(同一时间只有一个线程能上锁成功),而且通过对象内部存储的markword标记锁状态。 synchronized加锁方式
- 同步实例方法,锁是当前实例对象
- 同步类方法,锁是当前类对象
- 同步代码块,锁是括号里面的对象
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,他会维护一个队列,先到先得,就像排队上地铁,文明多了。