繁体   English   中英

Java 线程可见性和同步

[英]Java Thread Visibility and Synchronized

我一直在用一个流行的例子来试验 Java 线程可见性问题,这个例子是通过共享的布尔值和非易失性变量向线程发送停止信号,而目标线程似乎没有得到它)​​,如下所示:

public class ThreadVisibilityTest {

    //Shared variable to send a signal to the thread
    static boolean stopped = false;

    public static void main(String[] args) throws Exception {

        Thread targetThread = new Thread(new Runnable() {
                public void run() {
                while(!stopped) {}
                System.out.println("Target thread gets signal and stops...");
                }
                });

        targetThread.start();

        TimeUnit.SECONDS.sleep(5);
        stopped=true;
        System.out.println("Main thread has sent stop signal to the thread...");

    }

}

主线程通过将stopped设置为true,5秒后向目标线程发送停止信号,目标线程获取不到,因此不会停止。

stopped变量定义为volatile显然可以解决问题。

Bu 然后我意识到,如果我将stopped的变量non volatile ,而是在目标线程的synchronized上下文中访问它,目标线程将获得最终值并停止。 所以线程可见性问题似乎就像使用volatile一样得到解决。

Thread targetThread = new Thread(new Runnable() {
            public void run() {
                while(true) {
                    synchronized(this) {
                        if(stopped) break; 
                    }
                }
                System.out.println("Target thread gets signal and stops...");
            }
        });

而且用于同步的对象监视器似乎也没有效果,如下所示:

synchronized(Thread.class) {
        if(stopped) break; 
}

这是偶然发生的事情还是我错过了什么? 或者我们可以说访问具有互斥性的共享变量似乎会强制目标线程刷新其缓存内存,就像访问volatile变量一样?

如果后者是真的,您建议通过 volatile 关键字或互斥访问来克服线程可见性问题的哪种方式?

提前致谢

这是偶然发生的事情还是我错过了什么?

您错过了 Java 语言参考 (JLS) 中讨论 Java 内存模型的章节。 要么,要么你错过了 Java 教程中的并发章节。 https://docs.oracle.com/javase/tutorial/essential/concurrency/

无论哪种方式,您都会了解到,如果线程 A 从同步块中退出,然后线程 B 进入在同一对象上同步的块,则线程 A 在释放锁之前写入的所有内容都保证对线程 B 可见在线程 B 锁定锁之后。

我认为互斥还提供了内存可见性,如 Java Concurrency In Practice (By Brian Goetz) 在第 3.1.3 节锁定和可见性中所述。

看,您是在同步上下文中阅读它,但不是在同步上下文中写入。 这可能会导致问题。

正如其他答案已经指出的那样,使用同步时会建立内存可见性。

但是,最好使用 volatile 共享变量(除非非可见性相关问题需要同步)以获得更高的并发性。 过度使用同步会迫使线程不断地相互等待,而此时并发工作会更安全、更快。

同步块或方法的工作方式与关于可见性的 volatile 变量非常相似。 在线程退出同步块后,它释放监视器,这具有将 CPU 寄存器刷新到 CPU 缓存的效果 - 以便该线程所做的写入对其他线程可见。

在进入同步块之前,线程获取监视器,其作用是使本地处理器缓存(CPU 寄存器)无效,并强制线程从 CPU 缓存中重新读取。 因此,先前版本所做的所有更改对于具有相同监视器锁的线程都是可见的。

在上面的示例中,写入是在同步上下文之外进行的。 在这种情况下,根据 JMM,无法保证何时刷新 CPU 寄存器以及 - 因此,最新值是否可用于其他线程。

出于这个原因,您可以假设代码“工作”可能是因为刷新及时发生并且同步块强制线程在每个循环中重新从 CPU 缓存中读取。 此外,JMM 声明“ happens-before ”可见性保证需要相同的监视器锁。 因此,除此之外的任何事情都是偶然的。

来源: https ://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#synchronization

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM