1

参考下面的链接 http://docs.oracle.com/javase/tutorial/essential/concurrency/QandE/answers.html 下面的例子显示了Key Statement 1不保证在key Statement 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
  • 使用同步方法将消息封装在对象中。除非通过这些方法,否则永远不要引用消息。

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

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

另外,有没有办法修改代码以使 message="Mares do not eat oats" 在 message="Mares do eat oats" 之前执行?我能想到的一种方法是保留一个共享状态变量并在 message="Mares do not eat oats" 执行后设置它,而 message="Mares do eat oats" 应该在一个像 while(!stateVariable){ wait();} 然后更新消息。那正确吗?

谢谢。

4

3 回答 3

3

The happens-before thing doesn't guarantee that some statement is executed before some other statement. It guarantees that if a first statement is executed before a second one, (and there is a good chance of that happening in your exemple, since both threads start at the same time, and one sleeps 1 second whereas the other one sleeps 2 seconds) the second one will see what the first one has written.

The synchronized access to a variable guarantees that a write to this variable is visible to a subsequent read of this variable from another thread. Why? Because that's what the specification of the Java Memory Model says, and because the implementation respects the specification.

There are other ways to guarantee that: make the variable volatile, make it an AtomicReference, or store it and retrieve it from a concurrent collection.

于 2013-03-10T19:24:01.967 回答
3

不要将happens-before与任何特定的操作顺序相混淆。这种关系的要点是这样的排序根本存在而不是任何特定的排序。在您的示例中,没有排序:一个线程可能永远不会观察另一个线程的操作,即使您使用更多的睡眠语句并尝试阅读另一个线程所写的内容。

如果你想协调你的两个动作,不要诉诸wait/notify因为它们是非常低级和脆弱的机制。使用来自 的东西java.util.concurrent,例如 a CountDownLatch

于 2013-03-10T19:18:57.970 回答
2

在该站点中描述的答案中,它是说:

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

然后它说:

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

并不是说Mares do eat oats.一定会在结果中打印出来。!!
并且通过定义Synchronization保证可见性和原子性。因此,如果消息是通过对象的同步方法设置和获取的,那么它确保如果消息被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()主线程检索。

于 2013-03-10T20:32:49.410 回答