尽管是同步的,wait()和notify()是否仍然不可靠?

时间:2014-01-29 18:19:59

标签: java multithreading wait synchronized notify

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

E.g。在此代码中:

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

现在,问题是什么?好吧,如果正在运行的线程没有等待,他将不会看到任何通知(即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()
            }
        }
    }
}

这是正确的还是我错过了什么?它可以更容易吗?

3 个答案:

答案 0 :(得分:7)

  

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

右。这不是“不可靠”的情况,而是语言定义的情况。 notify()调用不会将通知排队。如果没有线程在等待,那么notify()将无效地执行任何操作。

  

可以更容易吗?

是。我会考虑使用BlockingQueue - LinkedBlockingQueue应该适合您。一个线程调用从队列调用,另一个可以添加到它。它将为您处理锁定和信号。一旦开始使用,您应该能够删除大部分手写代码。

答案 1 :(得分:0)

我一开始就被你的问题所困扰。 你在线程对象上的同步(this)没有意义。我过去也做这个东西让wait()不会抛出编译错误。

只有等待(任务)才有意义,因为您正在等待并想要获取此资源。

有一个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...

    }
}

答案 2 :(得分:0)

结果实际上不是死锁,而是任务/作业本身的饥饿。因为没有线程被“锁定”,所以在另一个线程调用do(Job job)之前,该任务才会完成。

您的代码几乎是正确的 - 除了在调用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!
    }
}

我们可以学到什么?好吧,在非阻塞线程中,不需要notify()wait()synchronized。并且设置wait(1)在空闲时甚至不使用更多的CPU(不设置wait(0),因为这将被视为wait()。

但是,请注意,因为使用wait(1)可能比使用wait()notify()更慢:Is wait(1) in a non-blocking while(true)-loop more efficient than using wait() and notify()?(换句话说:非阻止可能比阻止更慢! )