简体   繁体   中英

How to make sure all threads have finished before calling notify?

I'm trying to make sure that all my threads of Class A have finished before notify is called. At the moment if one thread has finished his work he calls notify (the threads of Class B) while other A threads are still running. If that happens, B Threads start working which is changing the condition for the remaining A Threads.

I've tryed to use synchronisation blocks but I guess Im using them wrong.

The idea is that A fills the Array until its full and notifys B so it can empty the array again.

public class A extends Thread {

    public static Object lockA = new Object();

    private ArrayList<String> list;

    public A(ArrayList<String> list) {
        this.list = list;
    }

    public void run(){

        while(true){
            synchronized (A.lockA){
                if(list.size() < 10){
                    list.add("A");
                    System.out.println(currentThread().getName() + " " + list);
                }else{
                    synchronized (B.lockB){
                        B.lockB.notifyAll();

                    }
                    return;
                }
            }

        }
    }

}
public class B extends Thread {

    public static Object lockB = new Object();

    private ArrayList<String> list;

    public B(ArrayList<String> list) {
        this.list = list;
    }

    public void run(){


            synchronized (B.lockB){
                try {
                    B.lockB.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                while (list.size() > 0){
                    list.remove("A");
                    System.out.println(currentThread().getName() + " " + list);
                }
                return;
            }
    }
}
public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        A a = new A(list);
        A aa = new A(list);
        A aaa = new A(list);
        B b = new B(list);
        B bb = new B(list);
        B bbb = new B(list);
        B bbbb = new B(list);

        a.start();
        aa.start();
        aaa.start();
        b.start();
        bb.start();
        bbb.start();
        bbbb.start();
    }
}

Unless lockB protects some state that a thread might want to wait for changes to or might need to notify another thread about changes to, using it in association with wait and notify doesn't make any sense.

        synchronized (B.lockB){
            try {
                B.lockB.wait();

You must never, ever call wait until you have checked to be sure that the thing you are waiting for has not already happened. The reason you have this code inside a synchronized block is precisely so that you can perform that check and wait if, and only if, you need to wait.

                synchronized (B.lockB){
                    B.lockB.notifyAll();
                }

This is pretty baffling. Why are you calling lockB.notifyAll() if you haven't changed anything protected by that lock that you need to notify other threads about?

It looks like you don't really understand how the wait / notify semantics actually work. The point is that you only need to wait if something hasn't happened yet and you can never be sure whether or not it's already happened (because schedulers are unpredictable) without holding some lock. But you need to release that lock before you wait, creating a race condition if some other thread does the thing you're waiting for after you release the lock but before you start waiting. The wait function is an atomic function to release a lock and wait for something to happen. The atomicity avoids this race condition.

However, you don't have the lock that's released protecting the thing you're waiting for. This destroys the logic of the atomic unlock and wait function and results in code that can always deadlock.

I would warp the list in a class that allows thread-safe updates of the list :

class ListWraper {
    //See also https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedList-java.util.List-
    private volatile List<String> list;

    ListWraper() {
        list = new ArrayList<>();
    }

    //add to list
    synchronized boolean add(String s){
        return list.add(s);
    }

    //remove from list
    synchronized boolean remove(String s){
        return list.remove(s);
    }


    //get a defensive copy of list
    List<String> getList() {
        return new ArrayList<>(list);
    }
}

A thread that uses ListWarper :

class ListUpdater extends Thread {

    private static String abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static int threadsCouter = 0;             //used for
    private final int threadNumber = threadsCouter++; //printing only
    private final Random rd = new Random();
    private final ListWraper list;

    ListUpdater(ListWraper list) {
        this.list = list;
    }

    @Override
    public void run(){
         for(int i = 0; i < 10; i++){
             //add random character to list 
             list.add(String.valueOf( abc.charAt(rd.nextInt(abc.length()))));
             try {
                TimeUnit.MILLISECONDS.sleep(100 + rd.nextInt(500)); //simulate long process 
            } catch (InterruptedException ex) { ex.printStackTrace();   }
         }
         System.out.println("Thread number " + threadNumber + " finished");
    }
}

And test it using:

public class Main{

    public static void main(String args[]) throws InterruptedException {
        ListWraper list = new ListWraper();
        ListUpdater updater1 = new ListUpdater(list);
        ListUpdater updater2 = new ListUpdater(list);
        updater1.start();
        updater2.start();
        updater1.join(); //wait for thread to finish 
        updater2.join(); //wait for thread to finish 
        System.out.println("All finished ");
    }
}

Output:

Thread number 1 finished
Thread number 0 finished
All finished

A complete runable code can be found here .

Another simple alternative is to use a shared CountDownLatch as demonstrated here .

I've tryed to use synchronisation blocks but I guess Im using them wrong.

In general, yes. For synchronization between multiple threads you should better use high-level primitives, that can be found in the package java.util.concurrent .

I'm trying to make sure that all my threads of Class A have finished before notify is called.

CountDownLatch fits directly to your case.

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