1.题目+说明
核心概念提取
- synchronized与可重入锁
- AQS
2.可重入锁(又名递归锁)
说明
“可重入锁”这四个字分开来解释
- 可:可以
- 重:再次
- 入:进入
- 锁:同步锁
- 进入什么:
- 进入同步域(即同步代码块/方法或显式锁锁定的代码)
- 一句话
- 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入
- 自己可以获取自己的内部锁
可重入锁种类
- 隐式锁(即synchronized关键字使用的锁)默认是可重入锁
- 同步块
- 同步方法
- synchronized的重入的实现机理
- 显式锁(即lock)也有 ReentrantLock这样的可重入锁
/**
* 可重入锁:可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
* <p>
* 在一个synchronized修饰的方法或代码块的内部
* 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
*/
public class ReEnterLockDemo {
static Object objectLockA = new Object();
//同步代码块
/*public static void m1() {
new Thread(() -> {
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------------------外层调用");
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------------------中层调用");
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------------------内层调用");
}
}
}
}, "t1").start();
}*/
//同步方法
/*public synchronized void m1() {
System.out.println("=======外");
m2();
}
public synchronized void m2() {
System.out.println("=======中");
m3();
}
public synchronized void m3() {
System.out.println("=======内");
}*/
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
//m1();
//new ReEnterLockDemo().m1();
new Thread(() -> {
lock.lock();
lock.lock();
try {
System.out.println("=====外层");
lock.lock();
try {
System.out.println("=====内层");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
//lock.unlock();//多个线程注释掉这一行试试
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "---------调用开始");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
3.LockSupport
LockSupport是什么
- 是什么
线程等待唤醒机制(wait/notify)
- 3种让线程等待和唤醒的方法
- Object类中wait和notify方法实现线程等待和唤醒
- 代码
- 正常
- 异常1
- wait方法和notify方法,两个都去掉同步代码块
- 异常情况
- 异常2
- 将notify放在wait方法前面
- 程序无法执行,无法唤醒
- 小总结
- 完整代码
public class LockSupportDemo { static Object objectLock = new Object(); static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) {//main方法,主线程一切程序入口 //1.场景1:先unpark,再park【park相当于形同虚设,可以注释掉】 //2.场景2:park和unpark不成对 Thread a = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t" + "-------------come in" + System.currentTimeMillis()); LockSupport.park();//被阻塞....等待通知等待放行,它要通过需要许可证 // LockSupport.park();//被阻塞....等待通知等待放行,它要通过需要许可证 System.out.println(Thread.currentThread().getName() + "\t" + "----------------被唤醒" + System.currentTimeMillis()); }, "a"); a.start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } Thread b = new Thread(() -> { LockSupport.unpark(a);//线程等待,让unpark先运行 // LockSupport.unpark(a);//线程等待,让unpark先运行 System.out.println(Thread.currentThread().getName() + "\t" + "----------------通知了"); }, "b"); b.start(); } /** * 场景1:注释掉lock和unlock * 场景2:先signal再await */ private static void lockAwaitSignal() { new Thread(() -> { //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t" + "--------come in"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒"); } finally { lock.unlock(); } }, "A").start(); new Thread(() -> { lock.lock(); try { condition.signal(); System.out.println(Thread.currentThread().getName() + "\t" + "----通知"); } finally { lock.unlock(); } }, "B").start(); } /** * 场景1:注释掉synchronized试试看效果 * 场景2:先notify(睡几秒),然后再wait */ private static void synchronizedWaitNotify() { new Thread(() -> { //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t" + "--------come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒"); } }, "A").start(); new Thread(() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t" + "----通知"); } }, "B").start(); }
- 代码
}
```
- Condition接口中的await后 signal方法实现线程的等待和唤醒
- 同Object类wait和notify
- 同Object类wait和notify
- 传统的synchronized和Lock实现等待唤醒通知的约束
- LockSupport类中的park等待和unpark唤醒
- 是什么
- 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
- 官网解释
- 主要方法
- API
- 阻塞
- park/park(Object blocker)
- 阻塞当前线程/阻塞传入的具体线程
- park/park(Object blocker)
- 唤醒
- unpark(Thread thread)
- 唤醒处于阻塞状态的指定线程
- unpark(Thread thread)
- API
- 代码
- 正常+无锁块要求
- 之前错误的先唤醒后等待,LockSupport照样支持
- 解释
- 解释
- 重点说明(重要)
- 面试题
- 是什么
4.AbstractQueuedSynchronizer之AQS
先从字节跳动及其它大厂面试题说起
- 面试题复盘
- 前置知识
- 公平锁和非公平锁
- 可重入锁
- LockSupport
- 自旋锁
- 数据结构之链表
- 设计模式之模板设计模式
- 是什么
- 字面意思
- 抽象的队列同步器
- 源代码
- 技术解释
- 字面意思
- AQS为什么是JUC内容中最重要的基石
- 和AQS有关的
- ReentrantLock
- CountDownLatch
- ReentrantReadWriteLock
- Semaphore
- ReentrantLock
- 进一步理解锁和同步器的关系
- 锁, 面向锁的使用者
- 定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
- 同步器,面向锁的实现者
- 比如Java并发大神DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等
- 锁, 面向锁的使用者
- 和AQS有关的
- 能干嘛
- 加锁会导致阻塞
- 有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
- 解释说明
- 加锁会导致阻塞
AQS初步
- AQS初识
- 官网解释
- 有阻塞就需要排队,实现排队必然需要队列
- 官网解释
- AQS内部体系架构
- AQS自身
- AQS的int变量
- AQS的同步状态State成员变量
- 银行办理业务的受理窗口状态
- 零就是没人,自由状态可以办理
- 大于等于1,有人占用窗口,等着去
- AQS的同步状态State成员变量
- AQS的CLH队列
- CLH队列(三个大牛的名字组成),为一个双向队列
- 银行候客区的等待顾客
- CLH队列(三个大牛的名字组成),为一个双向队列
- 小总结
- 有阻塞就需要排队,实现排队必然需要队列
- state变量+CLH变种的双端队列
- AQS的int变量
- 内部类Node(Node类在AQS类的内部)
- Node的int变量
- Node的等待状态waitState成员变量
- volatile int waitStatus
- 说人话
- 等候区其它顾客(其它线程)的等待状态
- 队列中每个排队的个体就是一个Node
- Node的等待状态waitState成员变量
- Node此类的讲解
- 内部结构
- 属性说明
- 内部结构
- Node的int变量
- AQS自身
- AQS同步队列的基本结构
从我们的ReentrantLock开始解读AQS
- Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成流程访问控制的
- ReentrantLock的原理
- 从最简单的lock方法开始看看公平和非公平
- 非公平锁走起,方法lock()
- 本次讲解我们走最常用的,lock/unlock作为案例突破口
- 源码解读比较困难,别着急—阳哥的全系列脑图给大家做好笔记
- AQS源码深度分析走起
- lock()
- acquire()
- 源码和3大流程走向
- 源码和3大流程走向
- tryAcquire(arg)
- 本次走非公平锁
- nonfairTryAcquire(acquires)
- return false;
继续推进条件,走下一个方法addWaiter
- return true;
结束
- nonfairTryAcquire(acquires)
- 本次走非公平锁
- addWaiter(Node.EXCLUSIVE)
- addWaiter(Node mode)
- enq(node);
- 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位
真正的第一个有数据的节点,是从第二个节点开始的
- enq(node);
- 假如3号ThreadC线程进来
- prev
- compareAndSetTail
- next
- addWaiter(Node mode)
- acquireQueued(addWaiter(Node.EXCLUSIVE),arg)
- acquireQueued
- 假如再抢抢失败就会进入
- shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法中
- shouldParkAfterFailedAcquire
- parkAndCheckInterrupt
- shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法中
- 假如再抢抢失败就会进入
- acquireQueued
- 方法unlock()
- sync.release(1);
- tryRelease(arg)
- unparkSuccessor
- 杀回马枪
- 杀回马枪
- unparkSuccessor
- tryRelease(arg)
- sync.release(1);
- lock()
彩蛋
1.synchronized-自动挡,lock-手动挡
2.从字节码角度分析synchronized实现
3.学习新技术,要对比学它前和学它后
4.jdk官方在线手册
5.学习三部曲(三把斧):理论+实操+小总结
避免成为api调用工程师