JUC学习笔记——共享模型之管程在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的管程部分
我们会分为以下几部分进行介绍:
- 共享问题
- 共享问题解决方案
- 线程安全分析
- Monitor
- synchronized锁
- Wait/notify
- 模式之保护性暂停
- 模式之生产者消费者
- park
- 线程状态转换详解
- 多锁操作
- 活跃性
- ReentrantLock
- 同步模式之顺序控制
共享问题概述我们首先来简单介绍一下贡献问题的产生原因:
- 操作系统目前只操纵一个CPU单位(单核CPU)
- 但是有两个线程都需要CPU来运行程序,所以操作系统采用时间片分配CPU
- 假设一个线程负责i++,一个线程负责i--,但我们需要注意共享数据的存放不是在线程中而是在内存里
- 假设一个线程取到数据,并进行i++操作之后,但并未将数据放入时,发生了上下文转换,这时另一个线程完成了i--操作
- 这时另一个线程的操作结果为0-1:-1,结果这个线程继续操作,将计算后的数据直接放入,结果变为了1,结果错误引发共享问题
// 针对counter,我们一个线程++,一个线程--各运行5000次static int counter = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter++;}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter--;}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",counter);}// 但结果却不是0,经常为-5000~5000之间的数
我们可以从底层代码分析问题:/*i++底层代码*/ getstatic i // 获取静态变量i的值iconst_1// 准备常量1iadd// 自增putstatic i // 将修改后的值存入静态变量i/*i--底层代码*/ getstatic i // 获取静态变量i的值iconst_1// 准备常量1isub// 自减putstatic i // 将修改后的值存入静态变量i
我们会发现他们的底层代码并不是一步实现,而是多步操作一同实现在单线程下,按照正常顺序实现自然不会出错:
文章插图
但是如果是多线程,就会因为上下文切换的缘由导致部分步骤出现交杂(我们给出正数示例):
文章插图
临界区和竞态条件首先我们来简单介绍一下临界区:
- 一段代码块内如果存在对共享资源的多线程读写操作 , 称这段代码块为临界区
- 例如我们上述共享问题中的i就是共享资源,而对i操作的i++和i--操作都可以被称为临界区
- 一个程序运行多个线程本身是没有问题的
- 多个线程读共享资源其实也没有问题
- 但是在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
共享问题解决方案总述我们的共享问题主要采用以下两种方案解决:
- 阻塞式的解决方案:synchronized , Lock
- 非阻塞式的解决方案:原子变量
- 俗称的【对象锁】,采用互斥的方式使目前至多只有一个线程能持有【对象锁】其它线程再想获取这个【对象锁】时就会阻塞住 。
- 这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
// 线程1,线程2 都使用同一对象作为锁,这样一个运行 , 另一个处于blocked阻塞synchronized(对象){临界区}
我们再给出相关代码示例:// 我们创建一个room对象来作为锁,注意处理共享问题的线程需要绑定同一个锁static int counter = 0;static final Object room = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (room) {counter++;}}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (room) {counter--;}}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",counter);}
我们做简单解释: