之前写了一篇剖析ReentrantLock源码的文章(超详细的源码分析)。
有反馈说,写的太长了!我也觉得写的很长,那就重新发一次,拆分版的。
前言
ReentrantLock 是基于 AQS
框架实现,JDK 中线程并发访问的同步技术,
它是一个互斥锁,也叫独占锁,支持可重入,支持锁的公平性。
大神Doug Lea
写的,整个AQS框架都是他一个人撸出来的,牛!
Doug Lea有多牛
就像:
- 谈喜剧电影,不可能不提星爷
- 谈相声,绕不开郭德钢
- 说中国教育,总得聊聊蔡元培
- 说到心理学,一定得说弗洛伊德
- 说到并发编程,Doug Lea不得不提
java1.6 之前, synchronized效率太低了,大神Doug Lea
就开发了AQS框架,就是解决并发问题。
JDK的出品公司SUN公司,一看,呀,这AQS性能那是相当可以,好吧,那就吸收进来,直接放JDK里吧。
synchronized可是SUN公司亲生的呀,可性能太差,SUN公司琢磨,要不优化下synchronized
于是乎,SUN公司的开发团队,历经数年,优化了synchronized,其性能大大提升,和AQS不相上下了。
从某种程度上说,Doug Lea 以一已之力,PK SUN公司整个开发团队,牛人!
这里从ReentrantLock
加锁解锁机制,由浅入深,让你彻底弄懂其原理。
一、应用场景
public static void main(String[] args) throws InterruptedException {
CountDownLatch downLatch = new CountDownLatch(1);
for(int i = 0; i < 10; i++){
new Thread(() -> {
try {
downLatch.await();
for(int j = 0; j < 1000; j++){
total++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000);
downLatch.countDown();
Thread.sleep(2000);
System.out.println(total);
}
这段代码,起10个线程,每个线程加 1000 次,期望值是 10000,但因为并发,最后会小于 10000
public static void main(String[] args) throws InterruptedException {
CountDownLatch downLatch = new CountDownLatch(1);
ReentrantLock lock = new ReentrantLock();
for(int i = 0; i < 10; i++){
new Thread(() -> {
try {
downLatch.await();
lock.lock(); // 加锁
for(int j = 0; j < 1000; j++){
total++;
}
lock.unlock(); // 解锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000);
downLatch.countDown();
Thread.sleep(2000);
System.out.println(total);
}
控制了并发访问,算出来的结果就是 10000
二、什么是AQS
简单讲,AQS(AbstractQueuedSynchronizer)
,是大神Doug Lea
写的,一个同步框架。
JDK 中 java.util.concurrent 包下,很多处理并发的工具类都直接或间接引用了AQS,比如ReentrantLock,Semaphore,CountDownLatch
……
AQS 这个抽象类中,有几个重要属性,咱先放在前面说一下。
- 首先说下,AQS 里面的
内部类Node
的几个属性
volatile int waitStatus; // Node里,记录状态用的
volatile Thread thread; // Node里,标识哪个线程
volatile Node prev; // 前驱节点(这个Node的上一个是谁)
volatile Node next; // 后继节点(这个Node的个一个是谁)
- AQS 本身的属性
private transient Thread exclusiveOwnerThread; // 标识拿到锁的是哪个线程
private transient volatile Node head; // 标识头节点
private transient volatile Node tail; // 标识尾节点
private volatile int state; // 同步状态,为0时,说明可以抢锁
这里画了张图,很直观,咱们照着图来说
当多个线程来竞争锁的时候,若抢锁失败,则生成一个Node,与 Thread 绑定,进入队列,该线程就被阻塞。
当锁释放时,唤醒节点去抢锁。
这个由Node对象组成的队列,叫CLH队列,是一个先进先出的队列,双向指针。