多线程中,线程1中本地线程中的值之后,什么时候才会刷新到主内存中去呢?什么有时候能刷新到主内存中去,有时候又不能刷新呢???
在多线程的环境下,如果某个线程首次读取共享变量,则首先到主内存中获取该变量,然后存入工作内存中,以后只需要在工作内存中读取该变量即可。同样如果对该变量执行了修改的操作,则先将新值写入工作内存中,然后再刷新至主内存中。但是什么时候最新的值会被刷新至主内存中是不太确定的,这也就解释了为什么VolatileFoo中的Reader线程始终无法获取到init_value最新的变化。
· 使用关键字volatile,当一个变量被volatile关键字修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然是先要修改工作内存,但是修改结束后会立刻将其刷新到主内存中。
· 通过synchronized关键字能够保证可见性,synchronized关键字能够保证同一时刻只有一个线程获得锁,然后执行同步方法,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存当中。
· 通过JUC提供的显式锁Lock也能够保证可见性,Lock的lock方法能够保证在同一时 刻只有一个线程获得锁然后执行同步方法,并且会确保在锁释放(Lock的unlock方法)之前会将对变量的修改刷新到主内存当中。
摘自:《Java高并发编程详解:多线程与架构设计》 — 汪文君
关于这本书的东西我写了一些代码,用于理解 echobai/thread,包括自己动手写一个可见锁和读写分离锁等
更新一:
public class Task implements Runnable {
public boolean flag = true;
@Override
public void run() {
while (flag) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "exit.");
}
public static void main(String[] args) throws InterruptedException{
Task task = new Task();
Thread t = new Thread(task, "task");
t.start();
TimeUnit.MILLISECONDS.sleep(10);
task.flag = false;
}
}
正常结束(连续5次测试都可以退出)
taskexit.
Process finished with exit code 0
public class Task implements Runnable {
public boolean flag = true;
@Override
public void run() {
while (flag) {
/*try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("working...");
}
System.out.println(Thread.currentThread().getName() + "exit.");
}
public static void main(String[] args) throws InterruptedException{
Task task = new Task();
Thread t = new Thread(task, "task");
t.start();
TimeUnit.MILLISECONDS.sleep(10);
task.flag = false;
}
}
也是正常退出(连续测试5次):
working...
working...
working...
working...
working...
working...
working...
working...
working...
working...
working...
working...
working...
working...
taskexit.
Process finished with exit code 0
不明白,你是哪里有问题,主线程退出时候,主线程写入主内存了就,但是如果主线程不退出,也不加锁,就以一定了
更新二:
确实是我错了,非常抱歉,再次改下代码,再看结果
public class Task implements Runnable {
public boolean flag = true;
@Override
public void run() {
boolean f = flag;
while (f) {
f = flag;
}
System.out.println(Thread.currentThread().getName() + "exit.");
}
public static void main(String[] args) throws InterruptedException{
Task task = new Task();
Thread t = new Thread(task, "task");
t.start();
TimeUnit.MILLISECONDS.sleep(10);
task.flag = false;
}
}
控制台不会退出,用jconsole
工具,看线程,main
退出,task
线程一直running
:
分析:某一线程只是对共享变量做读操作的话,在本地运行栈中做了缓存备份(其实此时main
线程已将变量写入主内存),加了锁或者volatile
以后,可以将缓存置空(或者无效),从而增加可见性,可以理解为不是写线程的问题,而是读线程的问题
更新 2020-08-17
最终答案可以看这里锁粗化
@tuesdayma
我搜到了这个问题,看了你们上面的评论:觉得stackoverflow上的一个回答应该能解决了为什么加上sleep就可以了
首先是官方文档:
然后相关的一个回答:
Let's see what the docs actually says: The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done.
See the highlighted word "free"? "free" means that the compiler can either read this.done once, or not. It's the compiler's choice. That's what "free" means. Your loop breaks because the compiler sees your code and thought "I am going to read iv.stop every time, even though I can read it just once." In other words, it is not guaranteed to always break the loop. Your code behaves exactly as the docs say.
12 回答5.8k 阅读
2 回答3.2k 阅读✓ 已解决
3 回答6.9k 阅读✓ 已解决
3 回答3k 阅读✓ 已解决
5 回答4.6k 阅读
4 回答2.2k 阅读
3 回答4.3k 阅读
我觉得不应该从刷新到主存这个方向去想这个问题,应该考虑JMM的Happens-Before规则。先看代码:
在没有注释掉①的情况下,输出的是
set run = false \n loop=1 \n end
,甚至loop=1要先输出。抽象一下 code1 --> volatile read --> volatile write --> code2 --> volatile read --> volatile write
由于这两个Happends-Before规则的存在,本来code1和code2是在不同的线程中的两块code,通过JMM的Happends-Before规则,使得code1发生的变化对code2可见。
对于这个代码而言,当Thread2 sleep的时候,other就已经开始read和write了
,当run=false之后other再次read的时候,Thread1就已经能“看到”run的修改了。