如何在多线程中使用等待和通知协议

发布于 2024-09-12 23:14:37 字数 955 浏览 6 评论 0原文

具体来说,有人可以告诉我这段代码有什么问题吗?它应该启动线程,因此应该打印“Entering thread..”5次,然后等待,直到调用notifyAll()。但是,它随机打印“正在输入..”和“完成..”,并且仍然继续等待其他人。

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

夜夜流光相皎洁 2024-09-19 23:14:37

您在方法调用方面没有做任何明显错误的事情,但是您有一个竞争条件

尽管在理想情况下,主线程将在所有工作线程到达 wait() 调用后到达其同步块,但不能保证这一点(您明确告诉虚拟机您没有希望线程通过使它们成为线程来与主线程按顺序执行)。线程调度程序可能会决定立即阻止所有工作线程(例如,如果您只有一个核心),以允许主线程继续运行。工作线程可能由于缓存未命中而被上下文切换。可能有一个工作线程因 I/O(打印语句)而阻塞,并且主线程被切换到其位置。

因此,如果主线程在所有工作线程到达 wait() 调用之前设法到达同步块,则那些尚未到达 wait() 调用的工作线程将无法按预期运行。由于当前设置不允许您对此进行控制,因此您必须添加对此的显式处理。您可以添加某种变量,该变量在每个工作线程到达 wait() 时递增,并且让主线程在该变量达到 5 之前不调用 notificationAll(),或者您可以让主线程循环并重复调用 notificationAll(),以便工作线程在多个组中释放。

看一下 java.util.concurrent 包 - 有几个锁类提供了比基本同步锁更强大的功能 - 一如既往,Java 使您免于重新发明轮子。 CountDownLatch 似乎特别相关。

总之,并发很难。您必须进行设计,以确保当线程按照您不想要的顺序以及您想要的顺序执行时,一切仍然有效。

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). It may happen (e.g. 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.

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. 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.

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. CountDownLatch would seem to be particularly relevant.

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.

失而复得 2024-09-19 23:14:37

我赞同 CountDownLatch 的建议。这是我用于多线程测试的模式:

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

这个想法是保证线程将执行的下一行代码是挑战多线程性或尽可能接近多线程性的代码。我将线程视为跑道上的跑​​步者。你让他们在赛道上(创建/开始),等待他们在起跑线上完美排列(每个人都调用了startingLine.countDown()),然后开枪(startPistol.countDown())并等待每个人冲过终点线 (finishingLine.countDown())。

[编辑] 应该注意的是,如果您没有想要在startingLine.await() 和startingPistol.countDown() 之间执行的任何代码或检查,您可以将startingLine 和startingPistol 合并到一个CyclicBarrier(threadCount + 1) 中。双 CountDownLatch 方法实际上是相同的,并且允许主测试线程在所有其他线程排列好之后以及它们开始运行之前(如果需要)进行任何设置/检查。

I second the CountDownLatch recommendation. 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()).

[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). 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.

花间憩 2024-09-19 23:14:37

您的代码的根本问题是,某些线程直到主线程调用 notifyAll 后才进入 wait。因此,当他们等待时,没有什么能叫醒他们。

为了使这项工作(使用等待/通知)工作,您需要同步主线程,以便它等待所有子线程进入可以接收通知的状态,然后再进行调用。

一般来说,使用 waitnotify 和原始锁进行同步是很棘手的。在大多数情况下,通过使用Java并发实用程序类,您将获得更好的结果(即更简单、更可靠和更高效的代码)。

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. 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.

Generally speaking, doing synchronization with the wait, notify and primitive locks is tricky. In most cases, you will get better results (i.e. simpler, more reliable and more efficient code) by using the Java concurrency utility classes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文