Java 在同步块中使用wait语句的奇怪错误

Java 在同步块中使用wait语句的奇怪错误,java,multithreading,Java,Multithreading,Worker.java文件的摘录: public class Worker extends Thread{ public void run(){ // Worker Thread periodically does its job. Master.getInstance().decrementNumOfWorkingWorkers(); // This is the reporting part of the th

Worker.java文件的摘录:

public class Worker extends Thread{

        public void run(){
                // Worker Thread periodically does its job.

       Master.getInstance().decrementNumOfWorkingWorkers();
        // This is the reporting part of the thread.
        // Aimed to wait other threads finish their job.
                synchronized (Master.getInstance().allFinished) {
            while (  Master.getInstance().getNumOfWorkingWorkers() > 0) {
                try {
                    Master.getInstance().allFinished.wait();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            Main.printSync("Worker Thread-" + getPId() + " worked on");
        }
        }
        }
这来自Master.Java:

import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

public class Master extends Timer {

    AllFinished allFinished;
    int day;
    public TimerTask task;
    LinkedList<Worker> Workers;
    private static Master instance = null;
    int numOfWorkingWorkers = 0;

    public class AllFinished
    {

    }

    public class PeriodicIncrement extends TimerTask {
        // Complete this class

        public void run() {

            Main.printSync("Day " + day + ":");
            Main.printSync("Queue: " + TaskQueue.getInstance().ConvertToString());

            day++;
            for (int i = 0; i < Workers.size(); i++) {

                synchronized (Workers.get(i)) {
                    Workers.get(i).notify();
                }
            }

            if (0 == numOfWorkingWorkers) {

                synchronized (allFinished) {
                    allFinished.notifyAll();
                }
                cancel(); // Terminate the timer thread
            }
        }
    }

    private Master(LinkedList<Worker> Workers) {
        super();
        this.task = new PeriodicIncrement();
        day = 0;
        allFinished = new AllFinished();
        this.Workers = Workers;
        numOfWorkingWorkers = this.Workers.size();
        this.schedule(task, 100, 100);
    }

}
import java.util.LinkedList;
导入java.util.Timer;
导入java.util.TimerTask;
公共类主控扩展计时器{
全部完成全部完成;
国际日;
公共时间任务;
社交网站工作者;
私有静态主实例=null;
int numOfWorkingWorkers=0;
公共课全部结束
{
}
公共类周期性增加扩展TimerTask{
//完成这门课
公开募捐{
Main.printSync(“日”+日+:”);
printSync(“队列:+TaskQueue.getInstance().ConvertToString());
day++;
对于(int i=0;i
对于4个辅助线程的测试,在我将摘录部分添加到worker.java中之前,一切都很好。然后,为了在所有工人完成后报告每个工人的行动,我添加了这一部分。算法非常简单。当工作人员完成其工作时,它会检查TaskQueue和ProductOwner中是否有任何工作。若并没有,它将中断其循环,然后在Master中减少1个活动工作线程计数器,然后调用Master的AllFinished字段上的wait。PeriodicIncrement的run()方法检查此计数器,如果它为0(意味着所有工作人员都完成了他们的工作),则在AllFinished上调用notifyAll()

问题是,有时有两个线程正在Worker.java中输入摘录的代码块,但remainings从未输入,所以活动的Worker线程计数器从未递减到0,我的程序也从未完成。 如果我只是注释掉Worker.java中的摘录部分,除了随机完成和报告之外,一切都很好。我的意思是摘录的部分似乎有问题


你能帮我找到答案吗?

在花了这么多时间没有使用低级并发原语之后,这是一个很有趣的调试。根本原因在于使用JDK提供的工具

╭───courtino ~
╰➤  sudo jstack -l 63978
2020-03-29 21:26:01
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):

"DestroyJavaVM" #18 prio=5 os_prio=31 tid=0x00007ffa91491000 nid=0x1803 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Timer-0" #17 prio=5 os_prio=31 tid=0x00007ffa91f36800 nid=0x5903 waiting for monitor entry [0x0000700009aa4000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.amazon.adnumsmissionmanagerservice.homework.ScrumMaster$PeriodicIncrement.run(ScrumMaster.java:42)
- waiting to lock <0x000000076bc70db8> (a com.amazon.adnumsmissionmanagerservice.homework.Programmer)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)

"Programmer-4" #15 prio=5 os_prio=31 tid=0x00007ffa92cc6800 nid=0x5603 in Object.wait() [0x000070000989e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.completeTasksUntilNoneAvailable(Programmer.java:230)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.work(Programmer.java:165)
- locked <0x000000076bc71a58> (a com.amazon.adnumsmissionmanagerservice.homework.Programmer)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.run(Programmer.java:241)

"Programmer-3" #14 prio=5 os_prio=31 tid=0x00007ffa923af800 nid=0x5503 in Object.wait() [0x000070000979b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.work(Programmer.java:176)
- locked <0x000000076bcc8ac0> (a com.amazon.adnumsmissionmanagerservice.homework.ScrumMaster$AllFinished)
- locked <0x000000076bc70db8> (a com.amazon.adnumsmissionmanagerservice.homework.Programmer)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.run(Programmer.java:241)

"Programmer-2" #13 prio=5 os_prio=31 tid=0x00007ffa92c25000 nid=0x3f03 in Object.wait() [0x0000700009698000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.work(Programmer.java:176)
- locked <0x000000076bcc8ac0> (a com.amazon.adnumsmissionmanagerservice.homework.ScrumMaster$AllFinished)
- locked <0x000000076bc5fca0> (a com.amazon.adnumsmissionmanagerservice.homework.Programmer)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.run(Programmer.java:241)

"Programmer-1" #12 prio=5 os_prio=31 tid=0x00007ffa91fab800 nid=0x4203 in Object.wait() [0x0000700009595000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.completeTasksUntilNoneAvailable(Programmer.java:230)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.work(Programmer.java:165)
- locked <0x000000076bc43c80> (a com.amazon.adnumsmissionmanagerservice.homework.Programmer)
at com.amazon.adnumsmissionmanagerservice.homework.Programmer.run(Programmer.java:241)

"Service Thread" #11 daemon prio=9 os_prio=31 tid=0x00007ffa91f30800 nid=0x4403 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #10 daemon prio=9 os_prio=31 tid=0x00007ffa9227c000 nid=0x3c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #9 daemon prio=9 os_prio=31 tid=0x00007ffa9227b000 nid=0x4603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #8 daemon prio=9 os_prio=31 tid=0x00007ffa92272800 nid=0x4803 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"JDWP Command Reader" #7 daemon prio=10 os_prio=31 tid=0x00007ffa9200f000 nid=0x3a03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"JDWP Event Helper Thread" #6 daemon prio=10 os_prio=31 tid=0x00007ffa91019800 nid=0x4a03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"JDWP Transport Listener: dt_socket" #5 daemon prio=10 os_prio=31 tid=0x00007ffa9181a000 nid=0x4b07 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007ffa9180d800 nid=0x3603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007ffa91002000 nid=0x3003 in Object.wait() [0x0000700008b77000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ab08ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076ab08ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007ffa92006800 nid=0x2e03 in Object.wait() [0x0000700008a74000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=31 tid=0x00007ffa90843000 nid=0x2d03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007ffa91001800 nid=0x2307 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007ffa91801800 nid=0x2a03 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007ffa91802000 nid=0x5303 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007ffa91802800 nid=0x5203 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007ffa91357800 nid=0x3d03 waiting on condition

JNI global references: 2236
这将把整个
工作
方法放入监视器属于
的同步块中。由于
Programmer
类是无状态的,并且它主要执行不与其他线程交互的工作,因此实际上可以同步
work
方法的一小部分。因此,您需要进行两项更改:

  • 工作
  • work
    方法内同步对
    wait
    的调用:

    synchronized (this) {
        wait();
    }
    

它可以提供的一个教训是,当使用synchronized块时,您总是希望同步尽可能少的代码。任何不需要同步的东西都应该在块之外,以便最大限度地提高并行性(块中发生的一切都是顺序的),从而减少锁的使用频率(如果将锁获取置于最低级别,则可能存在允许您跳过锁获取的条件,以便减少同步开销),在某些情况下,比如这一个,避免死锁。

哇,听起来我对这东西太生疏了,你是对的,它应该释放监视器,并在调用
等待之前拥有它。我暂时删除了我的答案,因为它具有误导性。让我感到困扰的是,我没有看到你减少活跃工作人员的数量。在哪里完成的?在Worker.java中添加了一行,将活动Worker递减1。顺便说一下,我通过将计数器的值打印到控制台来仔细检查它是否真的递减计数器。它是否达到0?如果您能够共享整个代码,我很乐意与您一起调试。我对限制感到厌烦,hahaNo,它没有达到0。Master.java文件中PeriodicIncrement类的run()方法甚至没有打印最后一个工作日。这意味着没有发送给工人的最后一个notify()。但是如果我在Worker.java中注释掉摘录的部分,它就会被发送。我想问的是,为什么要在定期任务中通知工人,谁在等待他们?
synchronized (this) {
    wait();
}