yokila
yokila
Published on 2021-11-28 / 6 Visits
0
0

JAVA线程执行过程中改变量值的结果引起的思考

前言

笔者在研究 volatile 关键词的使用时,意外发现了以下四种案例情况,感觉颇为神奇,于是进行记录。限于笔者才疏学浅,无法暂时无法更进一步研究问题的本质与底层的内容,所以希望能够抛砖引玉,得来更多更优秀深入的见解。

这是笔者的github的测试项目地址:点击前往,可以查看完整的代码。

1. 第一种情况

寻常情况下,线程执行过程中突然对其变量进行变动

这是最寻常的问题,run方法 中将 active 设为 true 后,while 一直用的就是这个 true ,不会去内存中刷新这个值,也就导致了 while死循环。

线程:

1-1

测试:

1-2

2. 第二种情况

对变量使用 volatile关键字 修饰情况下,线程执行过程中突然对其变量进行变动

加上 volatile关键字 后,每次使用到 active变量 都将拿到最新的值,所以 stop方法 改 active 的值后,while 就能拿到最新的 active值 为 false ,然后结束循环。

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

——《Java修饰符》

线程:

2-1

测试:

2-2

3. 第三种情况

这种情况比较特殊,在while循环中加入输入打印语句下,线程执行过程中突然对其变量进行变动

线程:

3-1

测试:

3-2

那么为什么输出语句能导致 run方法 的 while循环 发现 active 已经被修改了呢?

因为 println 里有用到了 synchronized 这个同步关键字。

如图:

3-3

而synchronized将导致

  1. 获得同步锁

  2. 清空工作内存

  3. 从主内存拷贝对象副本到工作内存

  4. 执行代码(计算或者输出等)

  5. 刷新主内存数据

  6. 释放同步锁

——《Java中System.out.println()为何会影响内存可见性》

所以真正的起作用的不是输出语句,而是 synchronized ,把输出语句换成什么都没做的同步区块也能实现相同的效果。

如图:

3-4

但是,这又产生了另一个问题:为什么在 stop方法 里用到输出语句(也是内部的 synchronized 起作用),不会影响 while 这个方法的 active 读取呢?难道作用的只是这个方法内数据刷新,而不是整个线程?

我将stop方法中的输出语句放在active修改后,想着是否能够因为synchronized的数据刷新使得while发现active已经变了,于是停下来,然而并没有。

如图:

那么,这又是为什么呢?

因为整个运行过程存在两个线程,一个是main线程,一个volatileTestThread2线程。

两个线程都有一个active的变量的副本在它们的工作内存中(真正的active是放在主内存中)

run方法在volatileTestThread2内调用运行,所以synchronized影响的是volatileTestThread2的工作内存里变量的与主内存之间的刷新

stop方法是main线程调用的,所以synchronized是影响main线程的工作内存与主内存之间的刷新。

二者是不同的。

这也就解释了“为什么在Stop方法里用到输出语句(也是内部的synchronized起作用),不会影响while这个方法的active读取呢?”这个问题。

4. 第四种情况

在while循环中加入Sleep下,线程执行过程中突然对其变量进行变动

线程:

4-1

测试:

4-2

我们都知道,sleep将使得线程进入休眠(等待状态),操作系统调度程序不调度睡眠线程以接收CPU时间。

那么等待状态结束后,线程进入就绪状态以及被调度选中后执行的这个过程,线程是否是进行了内存变量的刷新?或者是进行了某些底层处理,导致产生了和前文synchronized使用后一样的结果?

正在研究中...


Comment