欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AQS详解 1

程序员文章站 2022-05-04 20:28:48
...

1.本文不讲概念,用jdk8调试一步一步搞清AQS这个东西,请耐心并跟着本文实操

现在我们先实现下面这个类:

public class MutexLock {
	static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 3601133938511592218L;
        /***
         *  尝试获取锁
         */
        protected boolean tryAcquire(int arg) {
            // 当 state 值为0时,然后就改为1,否则就返回false也不修改state值
            if (compareAndSetState(0, 1)) {
                // 设置当前线程为 独占线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
		//尝试释放锁
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                // state 为 0 说明当前同步块中没有锁了,无需释放
                throw new IllegalMonitorStateException();
            }
            //当前独占线程 设置为null
            setExclusiveOwnerThread(null);
            // 将状态变量的值设为 0,以便其他线程可以成功修改状态变量从而获得锁
            setState(0);
            return true;
        }

        // 当前线程是否被独占
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }
    // 将操作代理到 Sync 上
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }
    /**
     * 试图释放此锁
     */
    public void unlock() {
        sync.release(1);
    }

上面代码仿照了ReentrantLock,实现了lock和unlock方法,实际操作都是Sync这个内部类操作的。
下面我们来测试下:

 public static void main(String[] args) throws InterruptedException {
        // final ReentrantLock lock = new ReentrantLock();
        final MutexLock lock = new MutexLock();
        ///  代码A
        lock.lock(); 
        new Thread() {
            public void run() {
                //代码B
                lock.lock();
                System.err.println("==============");
                lock.unlock();
            };
        }.start();
        Scanner sc = new Scanner(System.in);
        System.out.print("Please enter a string : ");
        String lein = sc.next();
        if (lein.equals("1")) {
            //代码C
            lock.unlock();
        }
    }

上面代码大致解释:

  1. 主线程加锁,然后子线程lock时拿不到锁会阻塞等待
  2. 主线程在控制台输入1 回车时,主线程unlock,子线程被唤醒直到执行完毕!

调试分析

1.代码A 块的执行

我们调用代码A 【lock.lock();】 方法,跟踪进到AQS类的acquire方法
AQS详解 1
执行子类的tryAcquire方法, 调试我们返回true。
AQS详解 1

返回上层函数,直接返回,到此main函数的代码A 【lock.lock();】 方法已经执行完毕!AQS详解 1
执行完总结:
AQS的两个成员变量
AQS详解 1
AQS详解 1
state 的值 变成1。
exclusiveOwnerThread的值变成 当前main函数线程的引用。

2.代码B块的执行

同样跟踪到下面tryAcquire方法,整个方法返回false
AQS详解 1
回到上层方法,进入addWaiter
AQS详解 1
addWaiter方法如下
AQS详解 1
执行enq方法之前我们观察Node的数据结构和值如下(记住)
AQS详解 1
进入enq方法,进入第一次循环。
AQS详解 1

第二次序号循环AQS详解 1
到此我们能想象出如下数据结构图
AQS详解 1
到此我们有了Node1节点Node2节点

继续走代码
AQS详解 1
返回上一层,执行acquireQueued函数AQS详解 1
acquireQueued函数的第一次循环AQS详解 1

下面我们看shouldParkAfterFailedAcquire这个方法
AQS详解 1

上面执行完后,需要知道Node1节点的waitStatus变量变成-1AQS详解 1

第二次循环,上面shouldParkAfterFailedAcquire返回false,所以直接进入第二次循环
AQS详解 1
第二次进入shouldParkAfterFailedAcquire方法
AQS详解 1
返回上一层AQS详解 1
进入parkAndCheckInterrupt方法
AQS详解 1
AQS详解 1

代码B块的执行总结
1.阻塞了当前子线程,利用LockSupport.park工具阻塞
2.在AQS内部构造了如下链表数据结构
AQS详解 1
今天就讲到这里吧,分析不清楚的敬请谅解。可以加我qq一起讨论。
老生常谈:深圳有爱好音乐的会打鼓(吉他,键盘,贝斯等)的程序员和其它职业可以一起交流加入我们乐队一起嗨。我的QQ:657455400 表演视频实例https://v.qq.com/x/page/f0517awx0x4.html