从ReentrantLock进入AQS

321 阅读2分钟

AQS 源码阅读

今天从 ReentrantLock 加锁的 lock 方法来一点点深入到 AQS 里面,看看AQS是如何达到加锁解锁的目的的,先测试只有一个线程加锁的话会发生什么,然后测试多个线程加锁如何处理,最后再看看unlock 操作是如何唤醒其他线程的。

只有一个线程

测试代码

public class TestReentantLock {
    static ReentrantLock lock = new ReentrantLock(true);
    static Runnable r1 = ()-> {
        // 断点位置
        lock.lock();
    };
    public static void main(String[] args) {
        new Thread(r1).start();
    }
}

执行流程

image-20220411194329982 image-20220411194455095 image-20220411194554405 image-20220411194743774 image-20220411195410278 image-20220411195543686 image-20220411195908861 image-20220411200033141

总结

通过这个流程可以的得到:

  1. 当只有一个线程的时候,是通过 CAS 来设置 state 得值,说明当前锁被某个线程持有
  2. 只有一个线程的时候,并没有涉及到节点及队列的操作

两个线程的时候

测试代码

package process;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentantLock {
    static ReentrantLock lock = new ReentrantLock(true);
    static Runnable r1 = ()-> {
        lock.lock();
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    };
    static Runnable r2 = ()->{
        // 断点位置
        lock.lock();
    };

    public static void main(String[] args) throws InterruptedException {
        new Thread(r1).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(r2).start();
    }
}

执行流程

image-20220411200636003 image-20220411200757320 image-20220411201532550 image-20220411201712606 image-20220411201851645 image-20220411202300875 image-20220411202515374 image-20220411202838888 image-20220411203147087 image-20220411203236845

总结

  1. 当有两个线程的时候,通过LockSupport.park() 来暂停线程,达到没有获取到锁的目的
  2. 通过设置前一个节点的 waitStatus 值为Node.Signal 为 -1,来表示要唤醒后继的节点,也就是自己。

unlock() 操作

测试代码

package process;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentantLock {
    static ReentrantLock lock = new ReentrantLock(true);
    static Runnable r1 = ()-> {
        lock.lock();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 断点处
        lock.unlock();
    };
    static Runnable r2 = ()->{
        lock.lock();
    };

    public static void main(String[] args) throws InterruptedException {
        new Thread(r1).start();
        TimeUnit.MILLISECONDS.sleep(500);
        new Thread(r2).start();
    }
}

执行流程

image-20220411204404870 image-20220411204504407 image-20220411204807717 image-20220411204944492 image-20220411205139959

总结

  1. 得到头节点,通过头节点的 waitStatus 判断解锁后续线程

总结

AQS 达到线程同步的方式,可总结为:

  1. 通过LockSupport.park() 阻塞线程,达到无法获取到锁的目的
  2. 通过 CAS 进行原子性的更新队列的 head 和 tail
  3. 通过一个 voliate 的 state,达到标识锁的状态的目的