【问题标题】:Java - Two threads communicating via wait()/notify() but threads are alternating on with thread gets lock firstJava - 两个线程通过等待()/通知()进行通信,但线程交替进行,线程首先获得锁定
【发布时间】:2013-04-04 09:13:11
【问题描述】:

这个程序的期望输出是:

平 乒乓 平 乒乓 平 乒乓

然而它在那个和之间交替

乒乓球 平 等等

问题是,我创建了 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");
    }
}

【问题讨论】:

  • 为什么不会发生?
  • 我没有正确地提出问题。我首先在 Ping 构造函数中创建 Ping 线程和类 start()。运行方法包括打印“Ping”语句。由于这是先完成的,我不明白为什么有时会先打印 Pong。

标签: java multithreading synchronization synchronized


【解决方案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 等待之前通知,则测试挂起。

【讨论】:

  • 想象thread1调用printer.notify();在thread2调用printer.wait()之前thread2得到通知并来到print.notify();明白我的意思了吗?我的测试显示会发生什么 - 通知丢失。
【解决方案2】:

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

丑陋的 hack,但可能有效:在启动第一个线程后但在启动第二个线程之前让出当前线程,以“鼓励”它运行。例如:

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

这是最简单的解决方案,但不能保证每次都能正常工作,例如,如果另一个线程(例如,事件处理程序)将在 yield 后获取控制权。

一种更稳健的方法:让主线程在启动第二个线程之前等待一些其他信号。假设此信号通过名为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...
}

【讨论】:

    【解决方案3】:

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

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

    这并不意味着t1 将在t2 之前开始执行。可能会,但t2 有可能首先开始。您必须编写自己的方法,保证 t1 首先开始。例如,在启动第一个线程后,wait() 在一个对象上,而在第一个线程notify() 上该对象上的run() 开头

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        JVM 不保证线程将按照它们启动的顺序启动。这就是为什么有时第 2 个线程会先启动。

        【讨论】:

          【解决方案6】:

          正如@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");
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多