synchronized 重量级锁分析


1. 背景
在JDK1.6以前,synchronized 的工作方式都是这种重量级的锁。它的实现原理就是利用 kernel 中的互斥量,mutex。主要是内核中的mutex 能够保证它是一个互斥的量。如果线程1拿到了 mutex,那么线程2就拿不到了。这是内核帮我们保证的。

至于为什么可以,可以去了解一下内核中的互斥量。

2. 为啥叫做重量级锁
内核需要去申请这个互斥量,必须要进入内核态。也就是这里需要用户态,内核态的切换。状态的切换,开销是比较大的。这就是重型锁的一个弊端。对于重型锁的一些主要的封装都是在 c 语言中的 pthread 这样一个库中,比如mutex_init, mutex_lock 等。之所以叫重量级锁,就是因为需要进入到内核态。

3. 能否在用户态实现一把互斥锁
我们不需要进入到内核态就能够获取到这样的一把锁,也就是在用户态就可以实现一把锁。

比如说,现在就有一把锁,就叫做 state。0:表示未被使用,1 表示锁被占用了。


lock 的实现

 

如果锁当前是被占用的状态,那么程序就一直死循环。如果某个时刻,有一个线程把锁释放了,那么就退出死循环,执行下一行 state = 1。而且持有者=当前线程。也就是 state = 0的时候,就可以任务当前线程可以拿到这把锁了。

unlock 的实现

 


释放锁的时候,首先需要判断一下当前持有锁的线程是不是当前线程。如果是当前线程,那么就将 state 置为0, 持有者 = null。

当前设计方式存在的问题
while(state==1); state = 1;其实可以认为这个是比较赋值俩步操作,先比较,符合条件了,再进行赋值。那么其实这不是原子性的。比如说在比较的时候,俩个线程同时进行了比较,这俩个线程同时发现state = 0,那么这俩个线程同时都去会执行 state = 1的操作,这俩个线程都认为自己拿到了锁,那么这个就产生了并发的问题了。

如何改进这个问题呢?
我们看到比较和赋值不是原子性的,在软件层面,我们也无法保证这俩步的原子性。所以,计算机给我们提供了原语,也就是 CAS。

那么我们来看一下如何改进呢? 我们把比较赋值这俩步操作,变成了一个原语操作,CAS。比较并交换。CAS中有三个参数,cas(state,0,1)。比较state的当前值是不是0,如果是0,那么就赋值为1。

 

再看看一下 unlock的操作。 在unlock操作中,其实也是一个比较赋值的操作,首先判断当前持有者是不是当前线程,如果是,再进行赋值。那为什么这里不需要用cas呢?因为必定是持有锁的线程才能执行到下一行。

 

而且赋值操作中,先赋值 持有者 = null。再赋值 state = 0。因为如果先执行 state = 0, 那么就相当于先释放掉这把锁了,另外一个线程就会执行 lock 成功,拿到锁,持有者 = 当前线程。那么就可能会带来一些问题。因为此时释放掉的锁并不是当前线程持有的锁。

这种锁也叫做自旋锁。

自旋锁的优点与缺点
自旋锁,spinLock。自旋锁不需要进入到内核态,整个程序的执行都是在用户态的,包括cas。但是cas不是计算机的原语吗?因为计算机的指令和用户态,内核态没有关系。

自旋锁当然也有缺点。 lock 操作如果一直没有拿到锁的话,会一直尝试。这是需要消耗cpu资源的。 所以自旋锁对于锁竞争比较激烈的情况下,是不适用的。

总结
mutex锁和自旋锁各有优缺点,那么我们能不能把这俩者结合一下呢? JDK 1.4 以前是借助内核中的 Mutex 互斥量; JDK1.4 以后是利用自旋锁,自旋n次以后,还是没有拿到锁的话,就切换到mutex。也就是将自旋锁和mutex进行了一个结合。因为mutex 加锁失败以后,会挂起,让出cpu资源。这样的话,算是对资源的一个合理利用。 JDK1.4以前,我们是可以设置自旋次数的,但是1.6以后,JDK可以自适应自旋,不用设置这个参数了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值