线程1:正在执行此循环
while(running) {
// Do Task()
}
println("Done");
线程2设置运行false 如果运行是一个volatile变量,则thread1退出循环并打印“Done”。
我的问题是,如果运行不是易失性的,那么Thread1何时从主内存中读取运行变量?
注意:我知道在关于同步和volatile变量的关系之前发生了,但是即使运行不是volatile或synchronized,线程1也会停止。所以我的问题是线程1何时决定从主存储器中读取,因为没有同步或没有挥发性
答案 0 :(得分:11)
在 Threads and Locks 部分的JLS中对此进行了描述。
当需要从主存储器读取线程时,根据同步顺序定义并在订单之前发生。基本上它表示为了使读取产生最后写入的值,写入需要在读取之前发生。< / p>
之前发生的关系粗略地讲是根据锁定/解锁操作定义的(并且有一些例外)归结为同步方法和块的使用。除非您正在处理volatile变量,否则底线通常是您需要同步对共享数据的所有访问,最好是通过AtomicBoolean
,BlockingQueue
或其他一些java.util.concurrent类。< / p>
17.4.4同步订单
每次执行都有一个同步顺序。同步顺序是执行的所有同步动作的总顺序。对于每个线程t,t中同步动作(第17.4.2节)的同步顺序与t的程序顺序(第17.4.3节)一致。
同步操作会在操作中引发 synchronized-with relation ,定义如下:
- 监视器m上的解锁操作与m上的所有后续锁定操作同步(后续操作根据同步顺序定义)。
- 对volatile变量(第8.3.1.4节)的写入v与任何线程的v的所有后续读取同步(其中后续根据同步顺序定义)。
- 启动线程的操作与其启动的线程中的第一个操作同步。
- 向每个变量写入默认值(零,false或null)与每个线程中的第一个操作同步。虽然在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时使用其默认初始化值创建的。
- 线程T1中的最终操作与另一个检测到T1已终止的线程T2中的任何操作同步。 T2可以通过调用T1.isAlive()或T1.join()来完成此任务。
- 如果线程T1中断线程T2,则T1的中断与任何其他线程(包括T2)确定T2已被中断的任何点同步(通过抛出InterruptedException或通过调用Thread.interrupted或Thread.isInterrupted) 。
同步边缘的来源称为释放,目的地称为获取。
17.4.5在订单之前发生
可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,那么第一个动作在第二个动作之前可见并且在第二个之前被命令。
如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前。
- 如果x和y是同一个线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)。
- 从对象的构造函数末尾到该对象的终结符(第12.6节)的开头有一个发生前的边缘。
- 如果某个操作x 与同步后续操作y,那么我们也有hb(x,y)。
- 如果hb(x,y)和hb(y,z),则hb(x,z)。
应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生。如果重新排序产生的结果与合法执行一致,则不是非法的。
更新:如果没有发生 - 在关系存在之前,线程永远不需要“刷新其缓存”。 This question并且它接受的答案提供了一个具体的例子。
以下是已接受答案的略微修改版本:
public class Test {
static boolean keepRunning = true;
public static void main(String[] args) throws InterruptedException {
(new Thread() {
public void run() {
while (keepRunning) {
}
}
}).start();
System.out.println(keepRunning);
Thread.sleep(1000);
keepRunning = false;
System.out.println(keepRunning);
// main thread ends here, but the while-thread keeps running.
// (but not if you change the keepRunning to volatile).
}
}
答案 1 :(得分:0)
为什么不亲自尝试?
public class ThreadTest {
private static boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
public void run() {
while( running ) {
System.out.println( "Running.");
}
long l = System.nanoTime();
System.out.println( "Stopped at "+l);
}
};
t.start();
Thread.sleep( 1 );
running = false;
long l = System.nanoTime();
System.out.println( "Stopping at "+l);
}
}
这绝不是一个完美的测试,但它给你一个粗略的估计。在我的机器上,差异大约是25毫秒。当然它也必须执行System.out.println
,但这肯定不会花那么长时间。
答案 2 :(得分:0)
我知道现在为时已晚,无法将其添加到详细解释的答案中。但是我希望有人能从我的答案中得到帮助。问题是关于Java / jvm线程缓存刷新何时发生?
当线程处于可运行状态时,jvm不会刷新或刷新本地线程缓存。但是在某些情况下,例如当线程自己或外部线程调度程序更改其状态时,则肯定是本地线程缓存将刷新到主内存。在这里,我只是调整了答案中给出的示例
(new Thread() {
public void run() {
while (keepRunning) {
LockSupport.parkUntil(500);
}
}
}).start();
System.out.println(keepRunning);
Thread.sleep(1000);
keepRunning = false;
System.out.println(keepRunning);
现在在这里,我只是将电流放入等待线程中。许多内置线程池执行程序的Java(例如通用线程池)使用此方法。因此,这里的线程将从主内存中读取并自行停止。
答案 3 :(得分:-1)
由于thread1已经读取了运行,它将具有缓存值,并且这将保持有效一段时间。这可能是我们或是ms,但可能更长。这可能取决于架构,也取决于盒子的繁忙程度。