当前位置:网站首页>【HBZ分享】AQS + CAS +LockSupport 实现ReentrantLock的原理
【HBZ分享】AQS + CAS +LockSupport 实现ReentrantLock的原理
2022-06-29 10:29:00 【hbz-】
根据ReentrantLock手写一把锁源码
手写源码gitee地址: https://gitee.com/huang_bao_se/write_aqs_cas_locksupport
CAS原理
- cas控制着3个值,分别是【内存地址V】【预期原值A】【改后新值B】
- 进行更改内存值时,会拿着【内存原值】与【期待原值】进行对比,如果内存地址V中保存的值和预期原值A相等,则处理器会自动将B的值替换到V上
- 如果V != A,则会更新失败。可以认为期间有人把V改了。一旦更新失败,会进行自旋,重新获取V的值,并重新计算B的新值,然后V继续和A继续比较,如果相等,就把B更新到V上
- AtomicInteger,AtomicXXX,等原子类底层就是CAS实现,一定程度比Synchonized好,因为后者属于悲观锁
- 缺点:当过多线程自旋,会导致CPU消耗,这种情况可能就比Synchonized差了。即很多线程情况下,Synchonized很有可能比CAS好
AQS加锁原理
- 第一次加锁,会获取锁,state加1。
- 此时线程B来了,获取锁失败了,他就会进入阻塞队列排队。然后线程C也来了,他也会获取失败, 他会插入到B的后面排队。
- 阻塞队列是一个双向链表,当线程A的state减到0了,此时会遍历这个阻塞队列(注意,是遍历,不是直接去下一个节点的线程)。那从里他最近的那个符合要求的节点获取线程来加锁。这块注意,如果B线程还活着,那就会拿B节点,之所以遍历而不是取下一个节点,就是防止B这个线程可能停止或者死了,如果停止了,那就会拿C。总之大体是先进先出,但线程一定要正常情况下
AQS解锁解锁
- 解锁首先state会减1, 如果state不为0,依然不会触发队列中的线程激活。
- 如果state为0了,锁彻底释放,然后遍历阻塞队列的NODE,取出符合要求离他最近的那个节点保存的线程来加锁。
ReentrantLock原理(非公平锁–默认–性能高于公平锁)
- ReentrantLock在接到一个新线程时,由于是非公平锁,所以直接会直接利用CAS进行判断加锁
- 当预期原值A = 0, 新值B = 1, 如果加锁成功,则直接返回true即可。并把state = 1,记录该线程重入次数
- 如果CAS返回false,说明已经有线程拿到了锁,此时判断【当前线程】与【加锁线程】是否时同一个线程,如果是,则state++, 然后返回true
- 如果CAS返回false,并且【当前线程】不等于【加锁线程】,则当前线程会进入阻塞状态,并且会装入【链表队列】中
- 多个线程来,会按照来的时间顺序,依次加入到【链表队列】中
- 解锁时,会把state–,如果state = 0, 表示锁释放,如果state-- 后不为0,则说明只是解锁了一层重入锁,此时锁并不能释放,链表队列中的线程依然不会唤醒
- 当state = 0时,,由于是非公平锁,会唤醒等待队列中所有的线程,所有的线程会进行抢锁,没抢到的继续回到队列,以此类推知道队列为空
ReentrantLock原理(公平锁)
- ReentrantLock在接到一个新线程时,由于是公平锁,所以先判断state = 0,而不会直接利用CAS加锁
- 如果确实为0, 则表示该锁此时未被任意线程获取,那么会通过CAS进行加锁,会加锁成功,拿到锁,然后state = 1;
- 如果state != 0, 会判断【当前线程】和【加锁线程】是否相等,如果相等,则state++, 否则进入等待队列
- 多个线程来,会按照来的时间顺序,依次加入到【链表队列】中
- 解锁时,会把state–,如果state = 0, 表示锁释放,如果state-- 后不为0,则说明只是解锁了一层重入锁,此时锁并不能释放,链表队列中的线程依然不会唤醒
- 当state = 0时,,由于是公平锁,所以只会唤醒等待队列中第一个线程,此时第一个线程拿到锁,同样操作以此类推,直到队列为空
手写ReentrantLock源码
/**
* 手写AQS
*/
public class MysLockAQS {
/**
* 是否加锁标识,锁状态(0:锁存在;1: 锁已给线程)
*/
private AtomicInteger lockState = new AtomicInteger(0);
/**
* 获取锁的对象
*/
private Thread thread = null;
/**
* 锁类型(true: 公平锁;false: 非公平锁)
*/
private boolean lockType = false;
/**
* 重入锁次数
*/
private Integer state = 0;
/**
* 没有获取锁得线程,保存到该链表
*/
private ConcurrentLinkedDeque<Thread> concurrentLinkedDeque = new ConcurrentLinkedDeque<Thread>();
public MysLockAQS(){
}
public MysLockAQS(boolean lockType){
this.lockType = lockType;
}
/**
* 加锁
* @return
*/
public boolean lock(){
// 这里死循环指的是,被阻塞得线程被唤醒后要不断地重试
for (;;){
// 通过CAS尝试加锁
if(acquire()){
// 加锁成功,thread赋予当前线程
thread = Thread.currentThread();
// state赋值1,表示加锁1次
state = 1;
// 加锁成功,直接退出循环
return true;
}else{
// 加锁失败,判断是不是当前线程
if(thread == Thread.currentThread()){
// 是当前线程,则依然放行,并且state+1
state++;
return true;
}
}
// 加锁失败,把该线程加入链表保存
concurrentLinkedDeque.add(Thread.currentThread());
// 阻塞当前线程,被unPark唤醒后,继续从这里执行,即进入下一次循环
LockSupport.park();
}
}
/**
* 解锁
* @return
*/
public boolean unLock(){
// 判断当前是否有线程拿到锁
if(thread == null){
return false;
}
// 判断如果是当前线程
if(thread == Thread.currentThread()){
// 判断state是否为1
if(state > 1){
// 大于1,则说明有重入锁,需要state - 1, 但不能释放锁
state--;
return true;
}
// CAS执行解锁
if(compareAndSetState(1, 0)){
// 解锁成功,判断是公平锁还是非公平锁
if(lockType){
// 公平锁,则唤醒链表中第一个线程
Thread first = concurrentLinkedDeque.pollFirst();
// 唤醒该线程
LockSupport.unpark(first);
}else{
// 非公平锁,循环唤醒链表所有线程,让他们自己抢占去
for(;;){
if(concurrentLinkedDeque.size() == 0){
System.out.println("队列已空");
break;
}
// 公平锁,则唤醒链表中第一个线程
Thread first = concurrentLinkedDeque.pollFirst();
// 唤醒该线程
LockSupport.unpark(first);
}
}
}
}
return false;
}
private boolean acquire(){
if(compareAndSetState(0, 1)){
return true;
}
return false;
}
/**
* CAS
* @param expect 期待原值
* @param update 更改新值
* @return
*/
private boolean compareAndSetState(int expect, int update){
return lockState.compareAndSet(expect, update);
}
调用测试:
public class Test {
private static MysLockAQS mysLockAQS = new MysLockAQS(true);
public static void main(String[] args) throws InterruptedException {
mysLockAQS.lock();
System.out.println("主线程加锁: " + Thread.currentThread().getName());
for(int i = 0; i < 10; i++){
// 开启10条子线程,进行公平锁与非公平锁校验
new Thread(()->{
System.out.println("start: " + Thread.currentThread().getName());
mysLockAQS.lock();
System.out.println("end: " + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
mysLockAQS.unLock();
}).start();
}
// System.out.println("主线程解锁: " + Thread.currentThread().getName());
test2();
mysLockAQS.unLock();
}
/**
* 重入锁测试
* @throws InterruptedException
*/
public static void test2() throws InterruptedException {
mysLockAQS.lock();
System.out.println("主线程2次加锁: " + Thread.currentThread().getName());
Thread.sleep(200);
test3();
mysLockAQS.unLock();
}
/**
* 重入锁测试
* @throws InterruptedException
*/
public static void test3() throws InterruptedException {
mysLockAQS.lock();
System.out.println("主线程3次加锁: " + Thread.currentThread().getName());
Thread.sleep(300);
mysLockAQS.unLock();
}
}
边栏推荐
- NUC980开源项目16-从SPI FLASH(W25Q128)启动
- (JS)数组方法:slice和splice
- MySQL query table field information
- (JS) iterator mode
- Modbus RTU protocol 485 learning 2-way infrared module
- 巴比特 | 元宇宙每日必读:HTC 宣布推出首款元宇宙手机,售价约2700元人民币,都有哪些新玩法?...
- NUC980开源项目16-从SPI FLASH(W25Q128)启动
- (JS) imitate the indexof method to find the position of a character in the string
- arcgis创建postgre企业级数据库
- crypto 1~5
猜你喜欢

在线文本过滤小于指定长度工具

Using EasyX configuration in clion

what? It's amazing that you can read the whole comic book for free. You can't learn to be a money saver together

Modbus RTU 协议485学习型2路红外模块

Encore une fois, le chemin de l'amélioration de redis Cloud

中科方德技术专家直播:如何基于 OpenStack、Ceph 构建私有云平台? | 第 27 期

ruoyi框架中添加sharding sphere5.0.0分表(通过spi添加自定义分表策略)

Today in history: musk was born; Microsoft launches office 365; The inventor of Chua's circuit was born

When the "Ai x scientific computing" is in progress, Huawei's mindspore competition question is hot, waiting for you!

Easydss is deployed on Disk C, and the video playback cannot be played normally. How to solve this problem?
随机推荐
TTL serial port learning infrared remote control module can be extended to network control
[3 questions per day (2)] minimum operand for generating alternate binary strings
Course design for the end of the semester: product sales management system based on SSM
Nuc980 started successfully
matlab基础 max 求一维或二维数组的最大值+sleep(pause)
Nuc980 open source project 16- start from SPI flash (w25q128)
The use of Fibonacci sequence and bubble sort in C language
X-FRAME-OPTIONS web page hijacking vulnerability
Limit introduction summary
关于IP定位查询接口的测评Ⅱ
(JS) responsibility chain mode
Map merges the same keys and values into a list
数据分析方法与思维:漏斗分析
Shell 中你不得不熟知的变量运用
EasyDSS部署在C盘,录像回看无法正常播放该如何解决?
西门子S7-200SMART控制步进电机的具体方法及示例程序
Pull and push ideas behind rxjs observable design principles
Nuc980 open source project 16- start from SPI flash (w25q128)
Mastering the clever use of some shell wildcards will make us write with half the effort
Add notification announcements to send notifications to online users