synchronized块锁定对象和wait / notify

时间:2015-08-13 12:54:38

标签: java multithreading synchronization

根据我的理解,当我使用synchronized块时,它获取对象的锁定,并在代码块执行完后释放它。在以下代码中

public class WaitAndNotify extends Thread{

    long sum;

    public static void main(String[] args) {
        WaitAndNotify wan = new WaitAndNotify();
        //wan.start();
        synchronized(wan){
            try {
                wan.wait();
            } catch (InterruptedException ex) {
                Logger.getLogger(WaitAndNotify.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println("Sum is : " + wan.sum);
        }
    }

    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<1000000; i++){
                sum = sum + i;
            }
            notify();
        }

    }  
}

如果run方法中的synchronized块首先获得锁,会发生什么?然后main方法中的synchronized块必须等待(不是因为wait(),因为另一个线程获得了锁)。运行方法执行完毕后,不知道主方法进入同步块并等待它永远不会得到的通知?我在这里误解了什么?

5 个答案:

答案 0 :(得分:2)

wait()隐式退出相应的监视器并在返回时重新输入:

请参阅wait()

  

当前线程必须拥有此对象的监视器。 线程发布   此监视器的所有权并等待另一个线程通知   在这个对象的监视器上等待通过a唤醒的线程   调用notify方法或notifyAll方法。线程然后   等到它可以重新获得监视器的所有权并恢复   执行

这就是为什么以及如何进行这种同步。

答案 1 :(得分:1)

是的,可以在导致挂起线程的notify()之前执行wait(),因此您需要小心它不会发生。

出于这个原因(以及其他人),使用java.util.concurrent的更高级别结构通常会更好,因为它们通常会减少你射击自己的可能性。

答案 2 :(得分:1)

你不会看到永远等待的&#39;问题在这里,因为你正在调用wait()的超时版本;因此,即使它没有收到通知,5秒后它也会返回。永远等待&#39; wait()调用的版本确实可能会出现您描述的问题。

答案 3 :(得分:1)

您在这里有两个线程:您的WaitAndNotify(WAN)线程和Java的主执行线程。两者都争夺同样的锁。

如果WAN线程首先获得锁定,则主线程将被阻止。处于阻塞状态与处于等待状态不同。处于等待状态的线程将在继续前进之前等待通知。处于阻塞状态的线程将在可用时主动尝试获取锁定(并继续尝试直到锁定状态)。

假设run方法正常执行,它将调用notify(),这将无效,因为当前没有其他线程处于等待状态。即使有,WAN仍然保持锁定,直到它退出同步的代码块。一旦WAN退出该块,THEN Java将通知一个等待的线程(如果有一个,则没有)。

此时,主执行线程现在获得锁定(它不再被阻止)并进入等待状态。现在,您已经使用了等待最多5000毫秒的等待版本,然后再继续。如果您使用了vanilla版本(wait()),它将永远等待,因为没有其他进程会通知它。

答案 4 :(得分:1)

这是一个示例程序的版本,它被改为引入一个测试条件变量的循环。通过这种方式,您可以避免在线程从等待中唤醒后重新获取锁定后对事物状态的错误假设,并且两个线程之间没有顺序依赖:

public class W extends Thread {
    long sum;
    boolean done;

    public static void main(String[] args) throws InterruptedException {
        W w = new W();
        w.start();
        synchronized(w) {
            while (!w.done) {
                w.wait();
            }
            // move to within synchronized block so sum
            // updated value is required to be visible
            System.out.println(w.sum);
        }
    }

    @Override public synchronized void run() {
        for (int i = 0; i < 1000000; i++) {
           sum += i;
        }
        done = true;
        // no notify required here, see nitpick at end
    }
}

等待通知是不够的,因为你指出了(顺序依赖,你依赖于竞争条件,希望一个线程在另一个线程之前获得监视器)以及其他原因。首先,线程可以在没有收到通知的情况下从等待中醒来,您不能认为存在通知呼叫。

当一个线程等待时,它需要在一个循环中这样做,在循环测试中它检查一些条件。另一个线程应该设置该条件变量,以便第一个线程可以检查它。 the Oracle tutorial提出的建议是:

  

注意:始终在测试等待条件的循环内调用wait。不要假设中断是针对您正在等待的特定条件,或者条件仍然是真的。

其他挑剔:

  • 在编写示例时,JVM不需要对主线程可见的sum变量进行更改。如果添加一个synchronized实例方法来访问sum变量,或者访问synchronized块中的和,那么主线程将保证看到更新的sum值。

  • 查看您的日志记录,没有任何关于InterruptedException的严重错误,它并不意味着出现任何问题。当您在线程上调用中断,设置其中断标志,并且该线程当前正在等待或正在休眠,或者在仍设置了标志的情况下进入等待或睡眠方法时,会导致InterruptedException。在我在这个答案顶部的玩具示例中,我将异常放在throws子句中,因为我知道它不会发生。

  • 当线程终止时,它会发出一个notifyAll,表示等待该对象的任何内容将被接收(再次,这就是如何实现连接)。使用Runnable而不是Thread更好的风格,部分原因在于它。

  • 在这个特定的例子中,在求和线程上调用Thread#join更有意义,而不是调用wait。

以下是重写使用连接的示例:

public class J extends Thread {
    private long sum;

    synchronized long getSum() {return sum;}

    public static void main(String[] args) throws InterruptedException {
        J j = new J();
        j.start();
        j.join();
        System.out.println(j.getSum());
    }

    @Override public synchronized void run() {
        for (int i = 0; i < 1000000; i++) {
           sum += i;
        }        
    }
}

Thread#join调用wait,锁定线程对象。当求和线程终止时,它发送通知并将其isAlive标志设置为false。同时在join方法中,主线程正在等待求和线程对象,它接收通知,检查isAlive标志,并意识到它不再需要等待,因此它可以离开join方法并打印结果。