Java - 两个线程通过wait()/ notify()进行通信,但线程交替使用,线程先获取锁定

时间:2013-04-04 09:13:12

标签: java multithreading synchronization synchronized

该程序的期望输出是:

平 傍 平 傍 平 傍

然而,它和

之间交替出现

傍 平 等

问题是,我创建了Ping线程并首先运行它。所以我不确定为什么Pong偶尔会先出现。

所以这是我的代码(易于编译)。它本质上是有效的。我只是不明白为什么它有时首先打印“Pong”。有人可以说明为什么会发生这种情况吗?

// Printer class - will be the object that both threads lock / synchronize onto
class Printer
{
    int numberOfMessages;
    int messageCount;

    // Constructor allowing user to choose how many messages are displayed
    Printer(int numberOfMessages)
    {       
        this.numberOfMessages = numberOfMessages;
        this.messageCount = 0;
    }

    // If more messages are to be printed, print and increment messageCount
    void printMsg(String msg)
    {
        if (messageCount < numberOfMessages)
        {
            System.out.println("[" + msg + "]");            
            ++messageCount;
        }
        else
        {
            System.exit(0);
        }
    }
}

// PingPong thread
class PingPongThread extends Thread
{
    Printer printer;
    String message; 

    public PingPongThread(Printer printer, String message)
    {
        this.printer = printer;
        this.message = message;
        this.start();
    }

    @Override
    public void run()
    {
        while(true)
        {
            synchronized (printer)
            {                   
                // Print message whether it is Ping or Pong
                printer.printMsg(message);

                // Notify
                printer.notify();

                // Wait
                try
                {
                    printer.wait();
                } 
                catch (InterruptedException e)
                {               
                    e.printStackTrace();
                }
            }
        }
    }


}

// Two threads communicate with eachother to alteratively print out "Ping" and "Pong"
public class PingPong
{
    public static void main(String args[])
    {
        Printer printer = new Printer(6);

        PingPongThread pingThread = new PingPongThread(printer, "Ping");
        PingPongThread pongThread = new PingPongThread(printer, "Pong");
    }
}

6 个答案:

答案 0 :(得分:1)

因为run方法在不同的线程中运行,除非你有适当的同步,否则你不能假设哪一个会先运行。如果线程首先启动它并不意味着它会更重要。 所有动物都是平等的。

您应该在线程中创建一个特殊标志,当线程启动时会通知该标志。并在启动第二个线程之前等待标志。简单的方法是使用Condition

答案 1 :(得分:1)

您想强制线程运行顺序。 JVM不保证,所以你必须自己动手。我可以想到两个解决方案。

丑陋的黑客,但可能有效:在启动第一个线程之后但在启动第二个线程之前产生当前线程,以“鼓励”它运行。 E.g:

PingPongThread pingThread = new PingPongThread(printer, "Ping");
Thread.yield();
PingPongThread pongThread = new PingPongThread(printer, "Pong");

这是最简单的解决方案,但并不能保证每次都能正常工作,例如,如果另一个线程(比如事件处理程序)在收益后获取控件,那就不会有效。

更强大的方法:让主线程在启动第二个线程之前等待其他一些信号。假设此信号通过名为lock的字段传递,这将类似于:

Object lock = new Object();
PingPongThread pingThread = new PingPongThread(lock, printer, "Ping");
lock.wait();
PingPongThread pongThread = new PingPongThread(lock, printer, "Pong");

并且线程run()方法将类似于

synchronize (lock) { lock.notify(); }
while (true) {
  // As before...
}

答案 2 :(得分:1)

JVM不保证线程将按启动顺序启动。这就是有时第二个线程首先开始的原因。

答案 3 :(得分:1)

如果你创建两个线程,例如t1t2,然后调用:

t1.start();
t2.start();

这并不意味着t1将在t2之前开始执行。它可能,但有可能t2首先开始。你必须编写自己的方法,保证t1首先启动。例如,在wait()

开头的第一个线程{@ 1}}开始后,对象上的第一个线程notify()

答案 4 :(得分:1)

    Printer printer = new Printer(6);
    PingPongThread pingThread = new PingPongThread(printer, "Ping");
    synchronized (printer) {
        printer.wait();
    }
    PingPongThread pongThread = new PingPongThread(printer, "Pong");

将保证pingThread始终首先启动。

然而,线程协调仍取决于谁跑得更快。考虑一下这个

final Object obj = new Object();
Thread t1 = new Thread() {
    public void run() {
        synchronized (obj) {
            obj.notify();
        }
    };
};

Thread t2 = new Thread() {
    public void run() {
        synchronized (obj) {
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
};

t1.start();
t2.start();

如果t1在t2等待之前通知,则测试挂起。

答案 5 :(得分:0)

正如@evgeniy在他的comment中指出的那样:标记为解决方案的答案有缺陷。一种可能的解决方案是在开始第一个线程之前锁定printer ,因此,在调用printer.wait()之前,它无法进入其同步部分。

Printer printer = new Printer(6);
synchronized (printer) {
    PingPongThread pingThread = new PingPongThread(printer, "Ping");
    printer.wait();
}
PingPongThread pongThread = new PingPongThread(printer, "Pong");