Java 如何正确使用条件来等待并通知所有人,而不丢失通知?

Java 如何正确使用条件来等待并通知所有人,而不丢失通知?,java,multithreading,Java,Multithreading,我有一组任务,它们被分为两部分,分别是enque和deque,以确保合格的(多个)首先运行,然后按顺序运行其他合格的(类似优先级队列) 在enque中,将检查任务,仅执行合格的任务,而阻止其他不合格的任务 在deque中,完成的任务将从队列中删除,然后notifyAll被阻止的任务(实际上是所有其他线程选择合格的) 以下是我试图实现的一个简化演示: class MyTaskQueue { private static final Object THE_QUEUE_LOCK = new O

我有一组任务,它们被分为两部分,分别是
enque
deque
,以确保合格的(多个)首先运行,然后按顺序运行其他合格的(类似优先级队列)

enque
中,将检查任务,仅执行
合格的
任务,而阻止其他
不合格的
任务

deque
中,完成的任务将从
队列中删除,然后
notifyAll
被阻止的任务(实际上是所有其他线程选择合格的

以下是我试图实现的一个简化演示:

class MyTaskQueue {
    private static final Object THE_QUEUE_LOCK = new Object();
    public static Map<String, ReentrantLock> taskGroupLock = new HashMap<>();
    public static Map<String, Condition> taskGroupCondition = new HashMap<>();

    public static void enque(String name, String taskId) {
        synchronized (THE_QUEUE_LOCK) {
            taskGroupLock.putIfAbsent(name, new ReentrantLock());
            taskGroupCondition.putIfAbsent(name, taskGroupLock.get(name).newCondition());
        }
        synchronized (taskGroupLock.get(name)) {
            while (true) {
                if (isValid(taskId)) {
                    break; // Go!!;
                } else {
                    try {
                        taskGroupCondition.get(name).wait(); // blocked if it's not allowed;
                    } catch (InterruptedException ignored) {
                        ignored.printStackTrace();
                    }
                }
            }
        }
    }

    public static void deque(String name, String taskId) {
        if (taskGroup.containsKey(name) && taskGroup.get(name).contains(taskId)) {
            synchronized (THE_QUEUE_LOCK) {
                taskGroup.get(name).remove(taskId);
                if (taskGroup.get(name).isEmpty()) {
                    taskGroup.remove(name);
                }
                synchronized (taskGroupLock.get(name)) {
                    taskGroupCondition.get(name).notifyAll();
                }
            }
        }
    }

}
类MyTaskQueue{
private static final Object_QUEUE_LOCK=new Object();
public static Map taskGroupLock=new HashMap();
public static Map taskGroupCondition=new HashMap();
公共静态void enque(字符串名称、字符串taskId){
已同步(队列锁定){
putIfAbsent(名称,new ReentrantLock());
taskGroupCondition.putIfAbsent(名称,taskGroupLock.get(名称).newCondition());
}
已同步(taskGroupLock.get(名称)){
while(true){
if(isValid(taskId)){
休息;//走!!;
}否则{
试一试{
taskGroupCondition.get(name).wait();//如果不允许,将被阻止;
}捕获(InterruptedException被忽略){
已忽略。printStackTrace();
}
}
}
}
}
公共静态void deque(字符串名称、字符串taskId){
if(taskGroup.containsKey(名称)和&taskGroup.get(名称).contains(任务ID)){
已同步(队列锁定){
taskGroup.get(name).remove(taskId);
if(taskGroup.get(name.isEmpty()){
任务组。删除(名称);
}
已同步(taskGroupLock.get(名称)){
taskGroupCondition.get(name.notifyAll();
}
}
}
}
}
目前,虽然我检查了所有其他任务(至少大多数)是否被正确阻止,但只执行第一个任务

但是当我检查
taskGroupCondition.get(name)
时,
firstwater
lastwater
都是
null

我错过了什么


任何帮助都将不胜感激

如果我理解正确,您会问以下问题:

  • 并行运行的线程请求从您的
    MyTaskQueue
    权限开始运行(通过
    enque
    方法)
  • enque
    MyTaskQueue
  • 的方法将阻塞,直到请求运行的任务得到确认
  • 每个限定线程通过调用
    deque
    方法向
    MyTaskQueue
    声明它已结束运行
  • deque
    方法通知所有其他任务,以便检查其中哪些任务是合格的,然后这些任务开始运行
  • 然后我可以看到以下解决方案:

    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Random;
    import java.util.Set;
    
    public class MyTaskQueue {
    
        private final Map<String, Set<String>> runningTasks;
        private String qualifiedTaskId;
    
        public MyTaskQueue(String initialQualifiedTaskId) {
            runningTasks = new HashMap<>();
            qualifiedTaskId = initialQualifiedTaskId;
        }
    
        private synchronized boolean isValid(String taskId) {
            return qualifiedTaskId != null && taskId != null && taskId.equals(qualifiedTaskId); //Do your qualification tests here...
        }
    
        public synchronized void setQualifiedTaskId(String qualifiedTaskId) {
            this.qualifiedTaskId = qualifiedTaskId;
            notifyAll(); //Now that the qualification test changed, is time to notify every blocked task.
            //This way, all new qualified tasks will also be started. This "notifyAll()" operation is optional.
        }
    
        public synchronized void enque(String task, String taskId) {
            while (!isValid(taskId)) { //Reentrant lock.
                System.out.println("Blocking unqualified task {\"" + task + "\", \"" + taskId + "\"}...");
                try { wait(); } catch (InterruptedException ie) { /*Handle the exception...*/ }
            }
            runningTasks.putIfAbsent(task, new HashSet<>());
            runningTasks.get(task).add(taskId);
            System.out.println("Starting qualified task {\"" + task + "\", \"" + taskId + "\"}...");
        }
    
        //Optional method. Might be needed for example if a Thread
        //wants to check if another task is currently running...
        public synchronized boolean isRunning(String task, String taskId) {
            return runningTasks.containsKey(task) && runningTasks.get(task).contains(taskId);
        }
    
        public synchronized void deque(String task, String taskId) {
            if (isRunning(task, taskId)) { //Reentrant lock.
    
                //Cleanup:
                runningTasks.get(task).remove(taskId);
                if (runningTasks.get(task).isEmpty())
                    runningTasks.remove(task);
    
                //Notify all blocked tasks:
                notifyAll();
            }
        }
    
        public static void main(final String[] args) {
            MyTaskQueue q = new MyTaskQueue("qualified");
            Random rand = new Random();
            new MyThread(q, "Task1", "qualified222", 2500 + rand.nextInt(500)).start();
            new MyThread(q, "Task2", "qualified222", 2500 + rand.nextInt(500)).start();
            new MyThread(q, "Task3", "qualified", 2500 + rand.nextInt(500)).start();
            new MyThread(q, "Task4", "qualified", 2500 + rand.nextInt(500)).start();
            new MyThread(q, "Task5", "foreverBlocked", 2500 + rand.nextInt(500)).start();
            try { Thread.sleep(3000); } catch (InterruptedException ie) { /*Handle the exception...*/ }
            synchronized (q) {
                System.out.println("Qualifying tasks of id \"qualified222\"...");
                q.setQualifiedTaskId("qualified222"); //Reentrant lock.
            }
            //Execution of main method never ends, because of the forever blocked task "Task5".
            //The "Task5" still runs while waiting for permission... See MyThread for details...
        }
    }
    
    MyThread
    是执行所需实际操作的类

    为简单起见,我假设一个任务的id等于一个变量(即
    qualifiedTaskId
    ),则该任务是合格的

    还有一种
    main
    方法来测试代码

    以下是示例输出(我对行进行了编号):

  • 正在阻止不合格的任务{“Task1”,“qualified222”}
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 正在启动合格任务{“Task4”,“合格”}
  • 任务{“Task4”,“限定”}当前正在运行
  • 正在启动合格任务{“Task3”,“合格”}
  • 任务{“Task3”,“限定”}当前正在运行
  • 正在阻止不合格的任务{“Task2”,“qualified222”}
  • 正在阻止不合格的任务{“Task2”,“qualified222”}
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 正在阻止不合格的任务{“Task1”,“qualified222”}
  • 正在阻止不合格的任务{“Task1”,“qualified222”}
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 正在阻止不合格的任务{“Task2”,“qualified222”}
  • id为“qualified222”的合格任务
  • 正在启动合格的任务{“Task2”,“qualified222”}
  • 任务{“Task2”,“qualified222”}当前正在运行
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 正在启动合格的任务{“Task1”,“qualified222”}
  • 任务{“Task1”,“qualified222”}当前正在运行
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 阻止不合格的任务{“Task5”,“blocked”}
  • 如您所见,第1行到第7行是每个线程的初始消息。
    然后,调用第8行到第10行,因为合格任务已结束(因此它们被重新阻止)。
    然后,调用第11到13行,因为另一个符合条件的任务已结束(因此它们被重新阻止)。
    然后,在第14行到第19行中,鉴定测试更改,新的鉴定任务开始运行。还有另一个任务(
    Task5
    )尚未通过鉴定。

    最后,调用第20到21行是因为任务id等于
    “qualified222”
    结束的合格任务。

    合格任务以并行或串行顺序运行?@thanopi57感谢您的帮助,是的,它们是并行的。优雅的解决方案。非常感谢你。
    public class MyThread extends Thread {
        private final String task, taskId;
        private final int actionTime; //Dummy uptime to simulate.
        private final MyTaskQueue q;
    
        public MyThread(MyTaskQueue q, String task, String taskId, int actionTime) {
            this.q = q;
            this.task = task;
            this.taskId = taskId;
            this.actionTime = actionTime;
        }
    
        @Override
        public void run() {
            q.enque(task, taskId); //Wait for permission to run...
            System.out.println("Task {\"" + task + "\", \"" + taskId + "\"} is currently running...");
    
            //Now lets actually execute the task of the Thread:
            try { Thread.sleep(actionTime); } catch (InterruptedException ie) { /*Handle the exception.*/ }
    
            q.deque(task, taskId); //Declare Thread ended.
        }
    }