Java 尽管已同步,wait()和notify()是否不可靠?

Java 尽管已同步,wait()和notify()是否不可靠?,java,multithreading,wait,synchronized,notify,Java,Multithreading,Wait,Synchronized,Notify,我最近发现,使用synchronized不会阻止任何死锁 例如,在本规范中: ArrayList <Job> task; ... public void do(Job job){ synchronized(tasks){ tasks.add(job); } synchronized(this){ notify(); } } public void run(){ while(true){ for

我最近发现,使用synchronized不会阻止任何死锁

例如,在本规范中:

ArrayList <Job> task;
...

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
    }
    synchronized(this){
        notify();
    }
}

public void run(){
    while(true){
        for (int = 0;i<tasks.size();i++){
            synchronized(tasks){
                Job job = tasks.get(i);
            }
            //do some job here...
        }
        synchronized(this){
            wait(); //lock will be lost...
            notifier = false; //lock will be acquired again after notify()
        }
    }
}
private volatile boolean notifier = false;
ArrayList <Job> task;
...

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
    }
    synchronized(this){
        notifier = true;
        notify();
    }
}

public void run(){
    while(true){
        for (int = 0;i<tasks.size();i++){
            synchronized(tasks){
                Job job = tasks.get(i);
            }
            //do some job here...
        }
        synchronized(this){
            if(!notifier){
                wait(); //lock will be lost...
                notifier = false; //lock will be acquired again after notify()
            }
        }
    }
}
现在,问题是什么?嗯,如果正在运行的线程没有等待,他将看不到任何通知,即notify调用,因此他可能会遇到死锁,无法处理收到的任务!或者他可能处理得太晚了

因此,我实现了以下代码:

ArrayList <Job> task;
...

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
    }
    synchronized(this){
        notify();
    }
}

public void run(){
    while(true){
        for (int = 0;i<tasks.size();i++){
            synchronized(tasks){
                Job job = tasks.get(i);
            }
            //do some job here...
        }
        synchronized(this){
            wait(); //lock will be lost...
            notifier = false; //lock will be acquired again after notify()
        }
    }
}
private volatile boolean notifier = false;
ArrayList <Job> task;
...

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
    }
    synchronized(this){
        notifier = true;
        notify();
    }
}

public void run(){
    while(true){
        for (int = 0;i<tasks.size();i++){
            synchronized(tasks){
                Job job = tasks.get(i);
            }
            //do some job here...
        }
        synchronized(this){
            if(!notifier){
                wait(); //lock will be lost...
                notifier = false; //lock will be acquired again after notify()
            }
        }
    }
}
这是正确的还是我遗漏了什么?能做得更容易些吗

现在,问题是什么?嗯,如果正在运行的线程没有等待,他将看不到任何通知,即notify调用,因此他可能会遇到死锁,无法处理收到的任务

对。这不是不可靠的情况,而是语言定义的情况。notify调用不会将通知排队。如果没有线程在等待,那么notify实际上什么也不做

能做得容易些吗


对。我会考虑使用BlockingQueue-a,这对你来说应该很好。一个线程调用从队列中拉出,另一个线程调用可以添加到队列中。它将为您提供锁定和信号。一旦您开始使用手写代码,您应该能够删除大部分手写代码。

一开始我被您的问题欺骗了。 线程对象上的同步没有意义。我过去也做过这件事,使等待不抛出编译错误

只有同步任务才有意义,因为您正在等待并希望获得这些资源

有一个for循环,这是一个糟糕的设计。消费者-生产者问题。在删除作业的同时获取作业。最好一次找一份工作

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
        notify();
    }
}

public void run(){
    Job job;
    while(true){

        //This loop will fetch the task or wait for task notification and fetch again.
        while (true){
            synchronized(tasks){
                if(tasks.size()>0){
                    job = tasks.getTask(0);
                    break;
                }
                else
                    wait();

            }
        }

        //do some job here...

    }
}

结果实际上并不是死锁,而是任务/作业本身的匮乏。因为没有线程被锁定,所以在另一个线程调用doJob作业之前,任务不会完成

除了调用wait和notify时丢失的异常处理之外,您的代码几乎是正确的。但您可以将task.size放在同步块中,也可以在钻孔过程中阻止任务,因为另一个线程删除任务中的作业会导致循环失败:

...
while(true){
    synchronized(tasks){
        for (int = 0;i<tasks.size();i++){ //could be done without synchronisation
           Job job = tasks.get(i);        //if noone deletes any tasks
        }
        //do some job here...
    }
    ...
请注意,您的代码是阻塞的。非阻塞可能更快,如下所示:

ArrayList <Job> tasks;
...

public void do(Job job){
    synchronized(tasks){
        tasks.add(job);
    }
}

public void run(){
    while(true){
        int length;
        synchronized(tasks){
            length = tasks.size();
        }
        for (int = 0;i<length;i++){
            Job job = tasks.get(i); //can be done without synchronisation if noone deletes any tasks...otherwise it must be within a synchronized block
            //do some job here...
        }
        wait(1); //wait is necessary and time can be set higher but never 0!
    }
}
我们能学到什么?在非阻塞线程中,不需要通知、等待和同步。设置wait1甚至不会在空闲时使用更多CPU。不要设置wait0,因为这将被视为等待


但是,要小心,因为使用wait1可能比使用wait和notify慢:换句话说,非阻塞可能比阻塞慢

编写线程安全代码很难。你应该使用现有的阻塞队列或只是执行器,而不是重新发明轮子。我倾向于同意。对于这样的任务列表,请使用已经实现的某种线程安全队列。也许这个:嗯,重新发明轮子更像是创造一种新的编程语言。所以,不,我不会重新发明轮子,因为编写代码更像是设计汽车,而不是发明轮子。不过,我喜欢法拉利而不是标致,法拉利追求的是速度和简洁;BlockingQueue是一种更有效、更好的模式@Marcus IMO。你至少应该了解它们。谢谢。我这么说不仅仅是为了得到分数,马库斯-+1感谢您认识到这是生产者/消费者的问题。如果您不喜欢此评论,我很抱歉,但wait100更像是热修复代码,而不是提供实际答案。他也可以使用信号灯或监视器来解决此问题。一次一步。了解如何使用wait和notify以及它的缺点,因此他会寻找其他更好的解决方案。你能仔细解释一下为什么wait100中的wait0吗?同意,非阻塞线程应该是wait0,或者类似wait5的东西……但有趣的解决方案,while循环中的while循环;修订过的。这应该是调用wait和notify的方式,有关同步的最佳教程之一可以在此处找到: