在特定示例的Java澄清中,发生在关系之前

在特定示例的Java澄清中,发生在关系之前,java,multithreading,concurrency,Java,Multithreading,Concurrency,参考以下链接 下面的示例显示,键语句1不能保证在键语句2之前执行 public class BadThreads { static String message; private static class CorrectorThread extends Thread { public void run() { try { sleep(1000); } catch

参考以下链接 下面的示例显示,键语句1不能保证在键语句2之前执行

public class BadThreads {

    static String message;

    private static class CorrectorThread
        extends Thread {

        public void run() {
            try {
                sleep(1000); 
            } catch (InterruptedException e) {}
            // Key statement 1:
            message = "Mares do eat oats."; 
        }
    }

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

        (new CorrectorThread()).start();
        message = "Mares do not eat oats.";
        Thread.sleep(2000);
        // Key statement 2:
        System.out.println(message);
    }
}
解决方案是 有两种方法可以保证消息的所有更改对主线程可见:

在主线程中,保留对correctorhread实例的引用。然后在引用消息之前在该实例上调用join 使用同步方法将消息封装在对象中。除非通过这些方法,否则不要引用消息。 这两种技术都建立了必要的“先发生后发生”关系,使消息的更改可见

我理解第一个使用join的解决方案如何使键语句1发生在键语句2之前。 但对于第二种解决方案,我无法理解使用同步方法(如getMessage和setMessage)如何建立这种关系。 如何保证修改后的密钥语句2 System.out.printlngetMessage将在修改后的密钥语句1 setMessageMares执行oats之后执行。 密钥语句2可能会在密钥语句1之前锁定消息,反之亦然,这取决于线程的调度方式

另外,是否有方法修改代码,使message=Mares不吃燕麦在message=Mares不吃燕麦之前执行? 我能想到的一种方法是保留一个共享状态变量,并在执行message=Mares不吃燕麦而message=Mares不吃燕麦时设置它,而message=Mares不吃燕麦应该像while一样处于一个受保护的块中!状态变量{wait;},然后更新消息。是这样吗

谢谢。

不要将“以前发生”与任何特定的操作顺序混淆。这种关系的要点是,这种排序根本存在,而不是任何特定的排序。在您的示例中没有顺序:一个线程可能永远不会观察到另一个线程的操作,即使您使用更多的sleep语句并尝试读取另一个线程所写的内容

如果你想协调你的两个行动,不要求助于等待/通知,因为这是非常低级和脆弱的机制。使用java.util.concurrent中的内容,例如CountDownLatch。

不要将“之前发生”与任何特定的操作顺序混淆。这种关系的要点是,这种排序根本存在,而不是任何特定的排序。在您的示例中没有顺序:一个线程可能永远不会观察到另一个线程的操作,即使您使用更多的sleep语句并尝试读取另一个线程所写的内容


如果你想协调你的两个行动,不要求助于等待/通知,因为这是非常低级和脆弱的机制。使用java.util.concurrent中的内容,例如CountDownLatch。

before不能保证某个语句在另一个语句之前执行。它保证如果第一条语句在第二条语句之前执行,并且在您的示例中很有可能发生这种情况,因为两个线程同时启动,一个线程休眠1秒,而另一个线程休眠2秒,那么第二个线程将看到第一条语句编写的内容

对变量的同步访问保证了对该变量的写入对于随后从另一个线程读取该变量是可见的。为什么?因为这就是Java内存模型的规范所说的,并且因为实现尊重规范


还有其他方法可以保证这一点:将变量设置为volatile,将其设置为AtomicReference,或者将其存储并从并发集合中检索。

before事件并不保证某个语句在另一个语句之前执行。它保证如果第一条语句在第二条语句之前执行,并且在您的示例中很有可能发生这种情况,因为两个线程同时启动,一个线程休眠1秒,而另一个线程休眠2秒,那么第二个线程将看到第一条语句编写的内容

对变量的同步访问保证了对该变量的写入对于随后从另一个线程读取该变量是可见的。为什么?因为这就是Java内存模型的规范所说的,并且因为实现尊重规范


还有其他方法可以保证这一点:将变量设置为volatile,将其设置为原子引用,或者将其存储并从并发集合中检索。

如该站点所述,其回答是:

然而,这一结果不能保证,因为没有 在键语句1和键之间的关系之前发生 陈述2。即使键语句1实际执行,这也是正确的 在关键语句2之前-请记住,在关系之前发生是关于可见性的,而不是顺序的

然后它说:

有两种方法可以保证对消息的所有更改 将对主线程可见:

这并不是说母马吃燕麦。一定会在结果中打印出来。!! 根据定义,同步保证了可见性和原子性。因此,如果消息是通过对象的同步方法设置和获取的,那么它可以确保如果消息是通过set方法更改的,那么get方法在读回消息时肯定会看到这个更改的值。因此,如果我们将代码更改为以下代码:


如果密钥语句1在密钥语句2之前执行,则它将保证通过主线程中的getMessage检索消息的最新更改值。

在该站点中描述的回答中,它表示:

然而,这一结果不能保证,因为没有 在键语句1和键之间的关系之前发生 陈述2。即使键语句1实际执行,这也是正确的 在关键语句2之前-请记住,在关系之前发生是关于可见性的,而不是顺序的

然后它说:

有两种方法可以保证对消息的所有更改 将对主线程可见:

这并不是说母马吃燕麦。一定会在结果中打印出来。!! 根据定义,同步保证了可见性和原子性。因此,如果消息是通过对象的同步方法设置和获取的,那么它可以确保如果消息是通过set方法更改的,那么get方法在读回消息时肯定会看到这个更改的值。因此,如果我们将代码更改为以下代码:


如果Key语句1在Key语句2之前执行,那么它将保证消息的最新更改值将通过主线程中的getMessage检索。

但是为什么使排序成为可能的第二个解决方案说将建立一个before关系,这样Key语句1将在Key语句之前执行语句2若您同步对消息的访问?不,这根本不是真的。再多的Thread.sleep也不能保证一条语句会在另一条语句之前执行。只有join和类似的协调技术才能实现这一点。因此,我的理解是正确的,上面粘贴的Oracle Java并发Q&A中提出的解决方案2不能保证键语句1在键语句2之前执行?确切地说。将synchronized添加到您发布的代码不会帮助它获得这两个操作之间顺序的保证。很容易看出:sleep1000实际上相当于在执行语句之前等待三秒钟。而且这也不仅仅是理论上的:当CPU负载很重时,这种计时很容易发生。但是为什么第二种使排序成为可能的解决方案说,将建立一个发生在之前的关系,这样,如果同步对消息的访问,键语句1将在键语句2之前执行?不,这根本不是事实。再多的Thread.sleep也不能保证一条语句会在另一条语句之前执行。只有join和类似的协调技术才能实现这一点。因此,我的理解是正确的,上面粘贴的Oracle Java并发Q&A中提出的解决方案2不能保证键语句1在键语句2之前执行?确切地说。将synchronized添加到您发布的代码不会帮助它获得这两个操作之间顺序的保证。很容易看出:sleep1000实际上相当于在执行语句之前等待三秒钟。这也不仅仅是理论上的:当CPU负载过重时,这种计时很容易发生。因此,上面的第二点是完全错误的:synchronized不提供任何保证,而不是join。OP的来源不可信。我认为同步位可能是为了简化。如果等待导致第一个线程在第二个线程之前到达同步块,那么HB就在那里。所以这有点半对,但我不会在生产中使用。@Marko,我非常困惑,因为我在Oracle/Sun concurreny教程中看到了这一点,它本应该是真理的来源:显然不是。真理的来源,是的,但不是全部真理,也不是唯一真理;-。作为一个教程,容易理解比完整性或最后细节的准确性更重要。权威参考不是java教程,而是java语言规范。@yshavit在高CPU负载或高虚拟内存交换情况下,很容易发生睡眠不足以执行所需顺序的情况。我认为这是一篇教育上被误导的学习材料。看看简化从哪里得到了我们的OP——这是正确的:所以上面的第二点是完全错误的:synchronized不提供任何保证,而不是join。OP的来源不可信。我认为同步位可能是为了简化。如果等待导致第一个线程在第二个线程之前到达同步块,那么它们是almos
t肯定会的-那么血红蛋白就在那里了。所以这有点半对,但我不会在生产中使用。@Marko,我非常困惑,因为我在Oracle/Sun concurreny教程中看到了这一点,它本应该是真理的来源:显然不是。真理的来源,是的,但不是全部真理,也不是唯一真理;-。作为一个教程,容易理解比完整性或最后细节的准确性更重要。权威参考不是java教程,而是java语言规范。@yshavit在高CPU负载或高虚拟内存交换情况下,很容易发生睡眠不足以执行所需顺序的情况。我认为这是一篇教育上被误导的学习材料。看看简化是从哪里得到我们的OP的——这是理所当然的:
class Message
{
    String message ;
    public Message(){}
    public Message(String message)
    {
        this.message = message;
    }
    public synchronized void setMessage(String message)
    {
        this.message = message;
    }
    public synchronized String getMessage()
    {
        return message;
    }
}
public class BadThreads {

    //static String message;
    static Message mess = new Message();
    private static class CorrectorThread extends Thread 
    {
        public void run() 
        {
            try 
            {
                sleep(1000); 
            } catch (InterruptedException e) {}
            // Key statement 1:
            mess.setMessage ("Mares do eat oats."); 
        }
    }

    public static void main(String args[]) throws InterruptedException 
    {
        (new CorrectorThread()).start();
        mess.setMessage("Mares do not eat oats.");
        Thread.sleep(2000);
        // Key statement 2:
        System.out.println(mess.getMessage());
    }
}