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

时间:2013-03-10 19:02:52

标签: java multithreading concurrency

参考以下链接 http://docs.oracle.com/javase/tutorial/essential/concurrency/QandE/answers.html 下面的示例显示密钥语句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);
    }
}

解决方案指出 有两种方法可以保证主线程可以看到对消息的所有更改:

  
      
  • 在主线程中,保留对CorrectorThread实例的引用。然后在引用消息
  • 之前调用该实例上的联接   
  • 使用同步方法在对象中封装消息。除非通过这些方法,否则不要参考消息。
  •   

这两种技术都建立了必要的先发生关系,使消息的变化可见。

我理解使用join的第一个解决方案如何使关键语句1“在之前发生”关键语句2。 但是对于第二个解决方案,我无法理解使用synchronized方法(比如getMessage()和setMessage())如何建立这种关系。 在修改后的密钥语句1(setMessage(“Mares do eat oats”))之后执行修改后的密钥语句2(System.out.println(getMessage())的保证是什么。 密钥语句2可以在密钥语句1之前获得锁定消息,反之亦然,具体取决于线程的调度方式。

另外,有没有办法修改代码,使message =“Mares不吃燕麦”在message =“Mares do eat oats”之前执行? 我可以想到的一种方法是保持一个共享的状态变量,并在message =“Mares不吃燕麦”之后设置它,而message =“Mares do eat oats”应该在一个块中保护,就像while(!stateVariable){ wait();}然后更新消息。是吗?

感谢。

3 个答案:

答案 0 :(得分:3)

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

如果你想协调你的两个动作,不要诉诸wait / notify因为那些是非常低级和脆弱的机制。使用java.util.concurrent中的内容,例如CountDownLatch

答案 1 :(得分:3)

之前发生的事情并不能保证在某些其他语句之前执行某些语句。它保证 if 第一个语句在第二个语句之前执行,(并且很有可能在你的例子中发生这种情况,因为两个线程同时开始,一个休眠1秒而另一个人睡2秒钟)第二个人会看到第一个写的东西。

对变量的同步访问保证了对此变量的写入对于从另一个线程后续读取此变量是可见的。为什么?因为这是Java Memory Model的规范所说的,并且因为实现符合规范。

还有其他方法可以保证:使变量变为volatile,使其成为AtomicReference,或者存储它并从并发集合中检索它。

答案 2 :(得分:2)

在该网站所述的答案中,它是这样说的:

  

然而,这个结果并不能保证,因为没有   发生在“密钥语句1”和“密钥”之间的关系之前   statment 2“。即使”密钥语句1“实际执行也是如此   在“关键语句2”之前 - 记住,之前发生的关系是关于可见性,而不是序列。

然后它说:

  

有两种方法可以保证对邮件的所有更改   将在主线程中可见:   

并不是说Mares do eat oats.肯定会在结果中打印出来。!!
通过定义Synchronization可以保证可见性和原子性。因此,如果设置了消息并通过对象的synchronized方法获取,那么它确保如果通过set方法更改消息,则在读取它时,get方法肯定会看到此更改的值。因此,如果我们将代码更改为此代码:

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());
    }
}

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