前言
在1.多线程基础 - 求知律己 - 博客园 (cnblogs.com)这篇博客中,我已经简要介绍了多线程的三种创建方式以及常用的方法,本篇博客我将讲解一下管程,提到管程,咋们首先要知道它是什么,管程即一个操作系统的资源管理模块,细化点说就是由共享数据结构和操作该结构的过程所组成的资源管理程序。它可以有效地解决死锁,这个后面会讲如何解决死锁。
1.管程之共享概念
1.1 共享问题
问题:两个线程对初始值为 0 的静态变量(临界区)一个做自增,一个做自减,各做 5000 次,结果是 0 吗?我们通过代码来验证下
代码实现
static Logger log = LoggerFactory.getLogger(ShareProblem.class); private 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.info("counter:{}", counter); }
运行结果


这是我测试之后截取的运行结果,几次结果都是负数,一次结果是正数,也有可能是0。我们是不是都期待结果是0,事实却不是如此,为什么呢,因为我们的代码里面的线程时并发执行的,是互相争夺资源的,所以它的运行在我们没有添加控制的时候,是不受我们控制的即共享变量的自增和自减不是原子操作,什么是原子操作呢?即不会被线程调度打断,也就是说不受线程状态变更影响。如果要分析上述问题的话,我们需要从代码执行的字节码开始分析。
对i++字节码操作为:
getstatic i // 1.获取静态变量i的值 iconst_1 // 2.准备常量1 iadd // 3.自增 putstatic i // 4.将修改后的值存入静态变量i
对i--亦是如此:
getstatic i // 1.获取静态变量i的值 iconst_1 // 2.准备常量1 isub // 3.自减 putstatic i // 4.将修改后的值存入静态变量i
而这些操作可以总结为:1)首先获取静态变量值,2)准备常量,3)进行操作,4)将修改值存入静态变量;其操作的过程都是在java的内存模型中间进行的,java的内存模型分为两种,一种是包含静态变量和成员变量的主存,另外一种是包含局部变量的工作内存。而完成i自增和自减是在主存和工作内存之间进行数据交换,交换过程如下图

继续分析上述操作,如果我们的代码变换成单线程执行上述加减操作的话,得到的结果就是如我们所想的0,执行过程图如下

但是如果我们是多线程的话就会出现交错运行,首先是出现负数的情况:

这是出现正数的情况:

一个程序执行多个线程是没有问题的,问题出在多个线程去访问同一个共享资源;多个线程区访问多个共享资源的时候容易出现上述指令交错的情况,即i++和i--是在进行完赋值操作之后进行了上下文切换导致改变的值并没有存入到主存中,引起了结果的错误。
一个代码块中如果存在对共享资源的多线程读写操作,我们将其称之为临界区。
static int counter = 0; static void increment() // 临界区 { counter++; } static void decrement() // 临界区 { counter--; }