AQS源码解读

以ReentrantLock进行AQS讲解
ReentrantLock有个Sync内部类继承AQS接口。ReentrantLock的公平锁与非公平锁都是继承Sync
在这里插入图片描述
在这里插入图片描述
ReentrantLock的加锁的三个步骤:
1.尝试加锁
2.加锁失败后,线程进入队列。
3.加入队列只会,进入阻塞状态。

通过实例进行讲解说明:创建三个线程模拟顾客访问网点需要进行加锁操作 在"线程1"里面等待5秒.

package com.example.demo.LockTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class AQSTest {
    public static void main(String[] args) {
        //分为公平锁和非公平锁
        ReentrantLock reentrantLock = new ReentrantLock();
//        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//        CountDownLatch countDownLatch = new CountDownLatch(4);
//        Semaphore semaphore = new Semaphore(4);
        //银行业务模拟AQS进行线程的管理和通知唤醒
        //使用三个线程模拟三个顾客进行网点操作
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"进入");
            reentrantLock.lock();
            try{
                try {
                    TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        },"线程1").start();
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"进入");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        },"线程2").start();
        new Thread(()->{
            reentrantLock.lock();
            try{
                System.out.println(Thread.currentThread().getName());
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        },"线程3").start();
    }
}

“线程1”进行lock会直接进入到ReentrantLock的Lock方法,因为是使用的非公平锁所以需要跳进的非公平锁的Lock,“线程1”是第一个进去的,所以state状态为0,直接进行运行。之后该线程进行睡眠。
"线程1"在进入休眠的时候,"线程2"进入了Reentrant的Lock方法,这个时候state为1,所以需要进入acquire()方法."线程3"也是同理.
在这里插入图片描述
acquire()方法进行解读:
tryAcquire():先尝试获得锁,当获取不到的时候,把当前线程放到Node里面添加到等到队列里面(AddWaiter),等待队列里面的线程尝试获取锁(acquireQueued)

在这里插入图片描述
tryAcquire()进行解读(这里是非公平锁实现的tryAcquire方法nonfairTryAcquire)
在这里插入图片描述
nonfairTryAcquire():如果刚好线程进来的时候,前一个线程运行结束则直接运行当前线程,进入(c==0)里面,如果当前线程就是运行线程,则setState(),所以state是可以大于1的,这里说的是重入锁。当前的情况为:"线程1"占着运行,state为1,"线程2"和"线程3"进入尝试获取锁,所以不符合这个两个情况所以直接返回false;线程2和线程3直接执行后面的方法:接下来对addWaiter()和acquireQueue()讲解.
在这里插入图片描述
aadWaiter():
“线程2”:把当前的线程封装到Node节点里面,设置当前线程的nextWaiter=Node.EXCLUSIVE,而Node.EXCLUSIVE为null。线程2进来是tail为null,所以直接进入enq()方法,执行完成之后返回当前节点(node),下面对enq()方法进行解析
“线程3”:把当前线程封装到Node节点里面,设置当前线程的nextWaiter=Node.EXCLUSIVE,而Node.EXCLUSIVE为null。线程3进来的时候tail不为null,所以进入if判断,现在的tail为“线程2”,设置当前node的前节点(prev)为“线程2”,并用CAS把Tail的值设置为node,再设置pred(线程2)的下一个节点(next)为当前节点(node),并返回当前节点(node)
在这里插入图片描述

enq():进行队列初始化,这个方法是个死循环方法
当最开始进入enq()方法是说明CLH队列没有任何的等待线程,所以tail为null,设置一个空的Node节点复制给Head,并把空的Node复制给tail。当二次进入循环的时候进入的是else方法,获取为空的tail,当前节点的前节点设置为一个空的节点,为空的节点的下一个节点设置为当前节点。并返回的是当前节点的前节点对象。
在这里插入图片描述
acquireQueue():当把阻塞线程添加到CLH队列里之后,需要在队列获取锁。
“线程2”:获取当前线程的前一个节点§,这里的p等于head,所有尝试获取锁,但是“线程1”一个在运行,state的状态为1,所有不进入if方法体,进入shouldParkAfterFailedAcquire方法(下面对shouldParkAfterFailedAcquire方法进行讲解),是为了设置当前线程的前一个线程的waitstatus为Node.SIGNAL,返回的是false,所以再次进行循环,再次进入shouldParkAfterFailedAcquire方法,返回的为true则进入parkAndCheckInterrupt(),使用的LockSupport.park()进行阻塞。线程被挂起。
“线程3”:线程三的前一个节点为线程2,而当前的head节点为空的node节点所以不会尝试获取锁。直接进入shouldParkAfterFailedAcquire方法(下面对shouldParkAfterFailedAcquire方法进行讲解),是为了设置当前线程的前一个线程的waitstatus为Node.SIGNAL,返回的是false,所以再次进行循环,再次进入shouldParkAfterFailedAcquire方法,返回的为true则进入parkAndCheckInterrupt(),使用的LockSupport.park()进行阻塞。线程并进行挂起。
在这里插入图片描述
shouldParkAfterFailedAcquire讲解:
如果waitStatus为Node.SIGNAL则直接放回true,如果是第一次进入,可能waitStatus为0,直接进入else,把当前的waitStstus设置为Node.SIGNAL
在这里插入图片描述
最后线程2和线程3会在parkAndCheckInterrupt方法里面一直被挂起,直到获取锁为至(当线程1执行unlock方法)
线程1执行unlock(),调用的是ReentrantLock里面的release()方法
执行tryRelease方法尝试释放锁(下面进行解析),如果返回true,判断是否存在CLH队列,如果不存在而为null,如果存在则有各个空的Node对象,并判断这个线程的等待状态是否需要唤醒,如果为0,则线程还没有阻塞,满足两个条件进入unparkSuccessor方法(下面进行解析),接触阻塞线程。
在这里插入图片描述
在这里插入图片描述
tryRelease()解析:这里调用的是Reentrant实现的tryRelease()
获取state状态减去释放的锁的数量,如果c==0表示当前没有线程在运行,设置free状态为True,把当前运行线程复制为null,设置state,返回free;如果c不等于0,说明当前线程没有执行完成,锁还没有完全释放,设置state,返回false
在这里插入图片描述
在这里插入图片描述
unparkSuccessor解析:
如果head的waitstatus<0,则把它的waitstatus设置为0,因为当前的head的waitStatus的SIGNAL为-1,所以能进这个if判断。获取head的下一个node节点为"线程2",线程2不为null,所以使用LockSupport来解放"线程2"
在这里插入图片描述
waitStatue状态:只有cancelled(取消为1)大于0
在这里插入图片描述
再此时,线程2,线程3 还一直在acquireQueue里面一直尝试获取锁。
线程2:能获取到锁,设置当前的head为本线程,设置之前的p为null以便GC回收。tryAcquire设置当前线程为线程所有者。
在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值