简体   繁体   English

如何使用多个线程的wait和notify协议

[英]How to use wait and notify protocol with multiple threads

Specifically, can somebody tell me what is wrong with this piece of code. 具体来说,有人可以告诉我这段代码有什么问题。 It should start the threads, so should print "Entering thread.." 5 times and then wait until notifyAll() is called. 它应该启动线程,所以应该打印“输入线程...”5次,然后等到调用notifyAll()。 But, it randomly prints "Entering.." and "Done.." and still keeps waiting on others. 但是,它会随机打印“输入......”和“完成......”,并且仍在等待其他人。

public class ThreadTest implements Runnable {
    private int num;
    private static Object obj = new Object();
    ThreadTest(int n) {
        num=n;
    }
    @Override
    public void run() {
        synchronized (obj) {
            try {
                System.out.println("Entering thread "+num);
                obj.wait();
                System.out.println("Done Thread "+num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        }   
    }   

    public static void main(String[] args) {
        Runnable tc;
        Thread t;
        for(int i=0;i<5;i++) {
            tc = new ThreadTest(i);
            t = new Thread(tc);
            t.start();
        }
        synchronized (obj) {
            obj.notifyAll();
        }
    }
}

You're not doing anything blatantly wrong with the method calls, but you have a race condition . 你没有对方法调用做任何明显的错误,但是你有竞争条件

Although in an ideal world the main thread will reach its synchronized block after all the worker threads reach the wait() call, there is no guarantee of that (you explicitly told the virtual machine that you didn't want the threads to execute in sequence with the main thread by making them threads). 虽然在一个理想的世界中,主线程将在所有工作线程到达wait()调用后到达其synchronized块, 但无法保证 (您明确告诉虚拟机您不希望线程按顺序执行用主线程制作线程)。 It may happen (eg if you have only one core) that the thread scheduler decides to block all the worker threads immediately they start to allow the main thread to continue. 可能会发生(例如,如果您只有一个核心)线程调度程序决定立即阻止所有工作线程,它们开始允许主线程继续。 It may be that the worker threads are context switched out because of a cache miss. 由于高速缓存未命中,工作线程可能是上下文切换出来的。 It may be that one worker thread blocks for I/O (the print statement) and the main thread is switched in in its place. 可能是一个工作线程阻塞I / O(print语句)并且主线程在其位置切换。

Thus, if the main thread manages to reach the synchronized block before all the worker threads have reached the wait() call, those worker threads that have not reached the wait() call will fail to operate as intended. 因此,如果主线程在所有工作线程到达wait()调用之前设法到达同步块,则那些尚未到达wait()调用的工作线程将无法按预期运行。 Since the current set up does not allow you to control this, you must add explicit handling of this. 由于当前设置不允许您控制此操作,因此必须添加对此的显式处理。 You could either add some sort of variable that is incremented as each worker thread reaches wait() and have the main thread not call notifyAll() until this variable reaches 5, or you could have the main thread loop and repeatedly call notifyAll(), so that worker threads are released in multiple groups. 您可以添加某种变量,当每个工作线程到达wait()并且主线程不调用notifyAll()直到此变量达到5,或者您可以让主线程循环并重复调用notifyAll()时,会增加某种变量。以便工作线程在多个组中发布。

Have a look in the java.util.concurrent package - there are several lock classes that provide more powerful capabilities than basic synchronized locks - as ever, Java saves you from re-inventing the wheel. 看一下java.util.concurrent包 - 有几个锁类提供比基本同步锁更强大的功能 - 一如既往,Java可以让你免于重新发明轮子。 CountDownLatch would seem to be particularly relevant. CountDownLatch似乎特别相关。

In summary, concurrency is hard . 总之,并发很难 You have to design to make sure that everything still works when the threads execute in the orders you don't want, as well as the orders you would like. 您必须设计以确保当线程在您不想要的订单中执行时,一切仍然有效,以及您希望的订单。

I second the CountDownLatch recommendation. 我是CountDownLatch推荐的第二个。 Here is the pattern I use for my multithreaded tests: 这是我用于多线程测试的模式:

final int threadCount = 200;

final CountDownLatch startPistol = new CountDownLatch(1);
final CountDownLatch startingLine = new CountDownLatch(threadCount);
final CountDownLatch finishingLine = new CountDownLatch(threadCount);

// Do a business method...
Runnable r = new Runnable() {
    public void run() {
        startingLine.countDown();
        try {
            startPistol.await();

            // TODO: challenge the multithreadedness here

        } catch (InterruptedException e) {
            Thread.interrupted();
        }
        finishingLine.countDown();
    }
};

//  -- READY --

for (int i = 0; i < threadCount; i++) {
    Thread t = new Thread(r);
    t.start();
}

// Wait for the beans to reach the finish line
startingLine.await(1000, TimeUnit.MILLISECONDS);

//  -- SET --

// TODO Assert no one has started yet

//  -- GO --

startPistol.countDown(); // go

assertTrue(finishingLine.await(5000, TimeUnit.MILLISECONDS));

//  -- DONE --

// TODO: final assert

The idea is to guarantee the very next line of code your threads will execute is the one that challenges the multithreadedness or as close to it as possible. 我们的想法是保证线程将执行的下一行代码是挑战多线程或尽可能接近它的代码。 I think of the threads as runners on a track. 我认为线程是赛道上的跑步者。 You get them on the track (create/start), wait for them to perfectly line up on the starting line (everyone has called startingLine.countDown()), then fire the starting pistol (startPistol.countDown()) and wait for everyone to cross the finish line (finishingLine.countDown()). 你让他们在赛道上(创造/开始),等待他们在起跑线上完美排队(每个人都叫做startingLine.countDown()),然后解雇起始手枪(startPistol.countDown())并等待所有人越过终点线(finishingLine.countDown())。

[EDIT] It should be noted that if you do not have any code or checks you want to execute between startingLine.await() and startingPistol.countDown(), you could combine both startingLine and startingPistol into one CyclicBarrier(threadCount + 1). [编辑]应该注意的是,如果你没有想要在startingLine.await()和startingPistol.countDown()之间执行任何代码或检查,你可以将startingLine和startingPistol组合成一个CyclicBarrier(threadCount + 1)。 The double CountDownLatch approach is effectively the same and allows the main test thread to do any setup/checks after all other threads are lined up and before they start running, should that be needed. 双重CountDownLatch方法实际上是相同的,并且允许主测试线程在所有其他线程排成一行并且在它们开始运行之前进行任何设置/检查,如果需要的话。

The root problem with your code is that some of the threads don't get to the wait until after the main thread calls notifyAll . 您的代码的根本问题是,在主线程调用notifyAll 之后 ,某些线程才会wait So when they wait, nothing will wake them up. 所以当他们等待时,没有什么能唤醒他们。

To make this work (using wait / notify) you need to synchronize the main thread so that it waits for all of the child threads to get to a state where they can receive the notify before it makes that call. 要使其工作(使用wait / notify),您需要同步主线程,以便它等待所有子线程进入可以在进行该调用之前接收通知的状态。

Generally speaking, doing synchronization with the wait , notify and primitive locks is tricky. 一般来说,与waitnotify和primitive锁同步是很棘手的。 In most cases, you will get better results (ie simpler, more reliable and more efficient code) by using the Java concurrency utility classes. 在大多数情况下,通过使用Java并发实用程序类,您将获得更好的结果(即更简单,更可靠和更高效的代码)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM