synchronized 重量级锁分析

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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可以自适应自旋不用设置这个参数了。

当然现在我们所说的都是重量级锁。包括mutext, 自旋锁自适应自旋锁。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6