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();
}
}
复制代码
执行流程
总结
通过这个流程可以的得到:
- 当只有一个线程的时候,是通过 CAS 来设置 state 得值,说明当前锁被某个线程持有
- 只有一个线程的时候,并没有涉及到节点及队列的操作
两个线程的时候
测试代码
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();
}
}
复制代码
执行流程
总结
- 当有两个线程的时候,通过LockSupport.park() 来暂停线程,达到没有获取到锁的目的
- 通过设置前一个节点的 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();
}
}
复制代码
执行流程
总结
- 得到头节点,通过头节点的 waitStatus 判断解锁后续线程
总结
AQS 达到线程同步的方式,可总结为:
- 通过LockSupport.park() 阻塞线程,达到无法获取到锁的目的
- 通过 CAS 进行原子性的更新队列的 head 和 tail
- 通过一个 voliate 的 state,达到标识锁的状态的目的