简体   繁体   中英

How to use Condition properly to wait and notifyAll without losing the notification?

I have a group of tasks and they are separated into two parts enque and deque to ensure the qualified (more than one) run first and then other qualified in order (something like priority queue).

Within the enque , tasks will be checked and only the qualified tasks will be executed while the other unqualified blocked.

Within the deque , tasks finished will be removed from the queue and then notifyAll the blocked (actually all other threads to select the qualified ).

Here is a simplified demo for what I am trying to achieve:

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

}

For now, only the first task will be executed though I checked all others (at least most of them) are properly blocked.

But when I checked the taskGroupCondition.get(name) , the firstWaiter and lastWaiter are both null .

Something I missed here?

Any help will be appreciated.

If I understood correctly, you are asking the following:

  1. Threads running in parallel request from your MyTaskQueue permission to start running (via the enque method).
  2. enque method of MyTaskQueue blocks until the task requesting to run gets qualified.
  3. Each qualified Thread declares to MyTaskQueue that it has ended running, by calling the deque method.
  4. deque method notifies all other tasks, so as to check which of them are qualified, which in turn start running.

Then I can see the following solution:

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

And follows MyThread :

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

MyThread is the class which performs the actual operations needed.

For simplicity, I supposed that a task is qualified if its id is equal to a variable (namely qualifiedTaskId ).

There is also a main method to test the code.

Follows sample output (and I numbered the lines):

  1. Blocking unqualified task {"Task1", "qualified222"}…
  2. Blocking unqualified task {"Task5", "blocked"}…
  3. Starting qualified task {"Task4", "qualified"}…
  4. Task {"Task4", "qualified"} is currently running...
  5. Starting qualified task {"Task3", "qualified"}…
  6. Task {"Task3", "qualified"} is currently running...
  7. Blocking unqualified task {"Task2", "qualified222"}…
  8. Blocking unqualified task {"Task2", "qualified222"}…
  9. Blocking unqualified task {"Task5", "blocked"}…
  10. Blocking unqualified task {"Task1", "qualified222"}…
  11. Blocking unqualified task {"Task1", "qualified222"}…
  12. Blocking unqualified task {"Task5", "blocked"}…
  13. Blocking unqualified task {"Task2", "qualified222"}…
  14. Qualifying tasks of id "qualified222"...
  15. Starting qualified task {"Task2", "qualified222"}…
  16. Task {"Task2", "qualified222"} is currently running...
  17. Blocking unqualified task {"Task5", "blocked"}…
  18. Starting qualified task {"Task1", "qualified222"}…
  19. Task {"Task1", "qualified222"} is currently running...
  20. Blocking unqualified task {"Task5", "blocked"}…
  21. Blocking unqualified task {"Task5", "blocked"}…

As you can see, lines 1 to 7 are the initial message for each thread.
Then, lines 8 to 10 are called because a qualified task ended (so they are re-blocked).
Then, lines 11 to 13 are called because another qualified task ended (so they are re-blocked).
Then, in lines 14 to 19 the qualification test changes and the new qualified tasks start running. There is still another task ( Task5 ) which is not qualified yet.
Finally, lines 20 to 21 are called because of the qualified tasks with task id equal to "qualified222" end.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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