如何确保在调用notify之前所有线程都已完成?

时间:2019-03-22 20:06:47

标签: java multithreading thread-safety

我正在尝试确保在调用notify之前我所有的Class A线程都已完成。当一个线程完成工作时,他调用notify(B类线程),而其他A线程仍在运行。如果发生这种情况,B线程将开始工作,这将改变其余A线程的条件。

我尝试使用同步块,但我想我错误地使用了它们。

这个想法是A填充数组直到其满,然后通知B以便它可以再次清空该数组。

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

3 个答案:

答案 0 :(得分:1)

除非lockB保护某个线程可能要等待更改或可能需要通知另一线程有关更改的状态,否则将其与waitnotify关联使用没有任何意义。

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

在确定要等待的事情尚未发生之前,请永远不要致电wait。您在synchronized块中包含此代码的原因正是为了使您可以执行检查,并且仅在需要等待时才执行。

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

这真是令人莫名其妙。如果您没有更改需要通知其他线程的受该锁保护的任何内容,为什么要调用lockB.notifyAll()

您似乎不太了解wait / notify语义实际上是如何工作的。关键是您只需要等待尚未发生的事情就可以等待,并且永远无法确定是否已经发生了(因为调度程序是不可预测的)而不持有任何锁定。但是您需要在等待之前释放该锁,如果在释放锁之后但在开始等待之前有其他线程执行了您要等待的操作,则会创建竞争条件。 wait函数是一个原子函数,用于释放锁并等待事件发生。原子性避免了这种竞争状态。

但是,您没有释放的锁来保护您正在等待的东西。这破坏了原子解锁和等待功能的逻辑,并导致代码始终死锁。

答案 1 :(得分:1)

我会在允许线程安全更新列表的类中扭曲列表:

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

使用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");
    }
}

并使用以下命令进行测试:

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

输出:

  

线程号1已完成
线程号0已完成
全部   完成

可以找到完整的可运行代码here

另一个简单的选择是使用CountDownLatch所示的共享here

答案 2 :(得分:1)

  

我尝试使用同步块,但我想我错误地使用了它们。

通常,是的。为了在多个线程之间进行同步,您最好使用高级原语,这些原语可以在软件包java.util.concurrent中找到。

  

我试图确保在调用notify之前我所有的Class A线程都已完成。

CountDownLatch直接适合您的情况。