JUC学习笔记——共享模型之管程

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我们会发现他们的底层代码并不是一步实现,而是多步操作一同实现
在单线程下,按照正常顺序实现自然不会出错:
JUC学习笔记——共享模型之管程

文章插图
但是如果是多线程,就会因为上下文切换的缘由导致部分步骤出现交杂(我们给出正数示例):
JUC学习笔记——共享模型之管程

文章插图
临界区和竞态条件首先我们来简单介绍一下临界区:
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
  • 例如我们上述共享问题中的i就是共享资源,而对i操作的i++和i--操作都可以被称为临界区
针对临界区我们需要注意以下内容:
  • 一个程序运行多个线程本身是没有问题的
  • 多个线程读共享资源其实也没有问题
  • 但是在多个线程对共享资源读写操作时发生指令交错,就会出现问题
然后我们再来介绍一下竞态条件:
  • 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
共享问题解决方案这小节我们将会介绍共享问题解决方案
共享问题解决方案总述我们的共享问题主要采用以下两种方案解决:
  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量
synchronized简述首先我们简单介绍一下synchronized: