当前位置: 首页 > 知识库问答 >
问题:

在Java内存模型中,同步和易失性是如何工作的?

云弘壮
2023-03-14

在《有效Java》一书中:

// Broken! - How long would you expect this program to run?
public class StopThread {

    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

后台线程不会在一秒钟后停止。因为提升,在JVM中优化,HotSpot服务器VM做。

您可以在以下主题中查看这一点:
为什么HotSpot会使用提升优化以下内容?。

优化过程如下:

if (!done)
    while (true)
        i++;

有两种方法可以解决这个问题。

private static volatile boolean stopRequested;

volatile的函数是
-禁止提升
-它保证任何读取该字段的线程都会看到最近写入的值

public class StopThread {

    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args)
                throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested())
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

上面的代码在有效Java书中是正确的,它相当于使用易失性来装饰停止请求

private static boolean stopRequested() {
    return stopRequested;
}

如果此方法忽略了synchronized关键字,则此程序无法正常运行
我认为,当方法忽略synchronized关键字时,这种变化会导致提升<是这样吗?

共有3个答案

厍建义
2023-03-14

声明易失性Java变量意味着:

这个变量的值永远不会被本地线程缓存:所有读写操作都将直接进入“主内存”;

声明同步的意思是:
所以,在易失性中只同步线程内存和主内存之间的一个变量的值,同步同步方法中线程内存和主内存之间的所有变量的值,并锁定和释放一个监视器来控制多个线程之间的所有权。

常坚
2023-03-14

不是这样的。当同步方法退出时,它会自动为同一对象的同步方法的任何后续调用建立一个先发生后发生的关系。这保证了对对象状态的更改对所有线程都是可见的。至于你上面的问题,<代码>易失性和<代码>同步都保证了可见性,使其在1秒后停止。

白浩荡
2023-03-14

为了清楚地理解为什么会发生这种情况,你需要了解更深层次的情况。(这基本上是对所谓的先发生后发生关系的解释,我希望这种语言对读者来说更不稳定)。

通常变量存在于内存中。当线程需要使用它们时,它会从内存中取出它们并将其放入缓存中,以便在需要之前尽可能快地访问它们。

使用volatile强制线程直接从RAM内存读取和写入变量。因此,当许多线程使用相同的volatile变量时,所有线程都会看到RAM内存中存在的最后一个版本,而不是缓存中可能存在的旧副本。

当一个线程进入一个synchronized块时,它需要控制一个监控变量。所有其他线程都会等待第一个线程退出synchronized块。为了确保所有线程都能看到相同的修改,同步块中使用的所有变量都直接从RAM内存读取和写入,而不是从缓存副本读取和写入。

因此,如果您试图读取变量stopRequested,而不使用synchronized方法或不使用volatile关键字,则可以读取缓存中可能存在的该变量的旧副本。

要解决这个问题,您需要确保:

  • 所有线程都在使用volatile变量
  • 或者所有访问这些变量的线程都在使用synchronized

使用该方法

private static boolean stopRequested() {
   return stopRequested;
}

如果没有synchronized关键字,并且stopRequested不是volatile则意味着您可以从无效的缓存副本中读取stopRequested的值。

 类似资料:
  • 我对易变语义几乎没有疑问 假设有三个线程T1、T2和T3,以及给定类的一个实例。 假设发生以下读/写操作序列: 我知道可以保证点9的T1将看到点7的值,点10的T1将看到点6的值(确切地说,至少和这个值一样最新)。 但是,这些说法是真的吗? Java内存模型保证,点11处的T1将看到至少与点5处的T3相同的最新值(来自T3或更实际的本地内存的值,但即使共享内存中有更实际的值,T1也可能看不到) 请

  • 我想澄清发生前关系是如何与不稳定变量一起工作的。让我们有以下变量: 和线程A: 和线程B: 根据Java内存模型(JMM),以下语句是否正确?如果不是,正确的解释是什么? 总是发生-之前 在JMM中发生在之前,只有当它实际发生在时间之前 在JMM中发生在之前(并且将被可预测地分配)如果实际发生在

  • 问题内容: 我对Java同步有疑问。我想知道我的类中是否有三个同步方法,并且一个线程在一个同步方法中获得了锁,另外两个将被锁定吗?我问这个问题是因为我对以下陈述感到困惑。 当线程处于对象的同步方法内部时,希望执行该同步方法或对象的任何其他同步方法的所有其他线程将必须等待。此限制不适用于已经具有锁并正在执行对象的同步方法的线程。这样的方法可以调用对象的其他同步方法而不会被阻塞。当然,任何线程都可以随

  • 我对Java同步有一个疑问。我想知道如果我的类中有三个同步方法,一个线程在一个同步方法中获取锁,其他两个会被锁定吗?我问这个问题是因为我与以下语句混淆了。 当一个线程在一个对象的同步方法内部时,所有希望执行这个同步方法或该对象的任何其他同步方法的其他线程都必须等待。这个限制不适用于已经有锁并正在执行该对象的同步方法的线程。这样的方法可以调用该对象的其他同步方法而不会被阻塞。该对象的非同步方法当然可

  • 问题内容: 首先,这是一个示例: 我没有得到的是堵塞如何发生。main函数启动两个线程,每个线程都开始各自的弓箭操作。 “同步”到底阻止了什么?为同一对象运行的功能相同(就像我最初想的那样)?同一类的所有对象具有相同的功能吗?同一对象的所有同步功能?同一类所有对象的所有同步功能? 问题答案: 在Java中,每个函数都提供了在其上锁定或锁定线程的功能。同步方法时,该方法将其对象实例用作锁。在你的示例

  • 最近我在读一些关于java并发的书。关于线程安全,如果不可能使一个类变为inmutable,那么可以通过同步它的数据来确保线程安全。 下面的类显然不是线程安全的 然后我可以同步写,但它不会保持线程安全 因为我不仅需要同步写入,还需要同步读取 现在的问题是,通过使用易失性,我可以保证其他线程会看到更新的值,所以这让我认为这个类应该是线程安全的 最后一个类线程安全吗??