简体   繁体   English

同步块锁定对象并等待/通知

[英]synchronized block locking object and wait/notify

According to what I understood, when I use a synchronized block it acquires the lock on an object and releases it when the code block is done executing. 根据我的理解,当我使用同步块时,它获取对象上的锁,并在代码块执行完毕后释放它。 In the following code 在下面的代码中

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

    }  
}

what happens if the synchronized block inside the run method acquires the lock first? 如果run方法中的同步块首先获取了锁,会发生什么情况? Then the synchronized block inside the main method has to wait (not because of the wait(), because the other thread acquired the lock). 然后,main方法中的同步块必须等待(不是因为wait(),因为另一个线程获得了锁)。 After the run method is done executing, won't the main method enter its synchronized block and wait for a notify which it will never get? 执行完run方法后,主方法是否不会进入其同步块并等待通知它永远不会得到? What did I misunderstand here? 我在这里误解了什么?

wait() implicitly exits the respective monitor temporarily and re-enters it upon returning: wait()隐式退出相应的监视器,并在返回时重新输入:

See wait() 参见wait()

The current thread must own this object's monitor. 当前线程必须拥有该对象的监视器。 The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. 线程释放此监视器的所有权,并等待直到另一个线程通过调用notify方法或notifyAll方法通知等待在此对象监视器上等待的线程唤醒。 The thread then waits until it can re-obtain ownership of the monitor and resumes execution . 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行

That's why and how this sort of synchronization does work at all. 这就是这种同步完全起作用的原因和方式。

Yes, it's possible to perform a notify() before a wait() causing a hung thread, so you need to be careful that it can't happen. 是的,有可能在导致线程挂起的wait() notify()之前执行notify() ,因此您需要注意不要发生这种情况。

For that reason (and others) it's generally better to use the higher level constructs of java.util.concurrent , since they generally give you less possibilities to shoot yourself in the foot. 出于这个原因(和其他原因),通常最好使用java.util.concurrent的更高级别的构造,因为它们通常使您脚下射击的可能性较小。

You won't see the 'waiting forever' issue here, because you are calling the version of wait() with a timeout; 您不会在这里看到“永远等待”的问题,因为您正在调用带有超时的wait()版本。 so, after 5 seconds it returns even if it doesn't receive a notify. 因此,在5秒钟后,即使没有收到通知,它也会返回。 The 'wait forever' version of the wait() call could indeed exhibit the problem you describe. 的“永远等待”版本的wait()调用确实可能表现出您描述的问题。

You've got two threads here: your WaitAndNotify (WAN) thread, and Java's main execution thread. 这里有两个线程: WaitAndNotify (WAN)线程和Java的主执行线程。 Both are vying for the same lock. 两者都在争夺同一把锁。

If the WAN thread gets the lock first, the main thread will be blocked. 如果WAN线程首先获得锁定,则主线程将被阻塞。 Being in a blocked state is NOT the same as being in a wait state. 处于阻塞状态与处于等待状态不同。 A thread in the wait state will wait for notification before moving forward. 处于等待状态的线程将在继续前进之前等待通知。 A thread in the blocked state will actively try to get the lock when it becomes available (and keep trying until it does). 处于阻塞状态的线程将在可用时主动尝试获取该锁(并一直尝试直到获得锁为止)。

Assuming the run method executes normally, it will call notify() , which will have no effect because no other threads are currently in a wait state. 假设run方法正常执行,它将调用notify() ,因为当前没有其他线程处于等待状态,因此该方法无效。 Even if there were, WAN still holds the lock until it exits the synchronized block of code. 即使存在,WAN仍会保持锁定状态,直到退出同步的代码块为止。 Once WAN exits the block, THEN Java would notify a waiting thread (if there was one, which there is not). 一旦WAN退出该块,那么Java将通知一个等待线程(如果有,则没有)。

At this point, the main execution thread now obtains the lock (it is no longer blocked) and enters the wait state. 此时,主执行线程现在获得了锁(不再被锁定)并进入等待状态。 Now you've used the version of wait that will wait up to 5000 milliseconds before continuing. 现在,您已经使用了等待版本,它将等待长达5000毫秒才能继续。 If you used the vanilla version ( wait() ) it would wait forever because no other process would notify it. 如果您使用原始版本( wait() ),它将永远等待,因为没有其他进程会通知它。

Here is a version of the example program changed to introduce a loop that tests a condition variable. 这是示例程序的一个版本,更改后引入了一个测试条件变量的循环。 This way you avoid bad assumptions about the state of things after a thread re-acquires a lock upon waking from a wait, and there's no order dependence between the two threads: 这样,您可以避免在线程从等待状态中唤醒后重新获取锁之后对事物状态的错误假设,并且两个线程之间没有顺序依赖性:

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

It's not sufficient to wait on a notification, for the reason you point out (order dependence, where you're relying on a race condition hoping one thread acquires the monitor before another) as well as for other reasons. 仅仅由于您指出的原因(顺序依赖性,您要依靠竞争条件希望一个线程先于另一个线程获取监视器)等原因而等待通知还不够。 For one thing, a thread can wake up from waiting without ever having received a notification, you can't assume that there was a notify call at all. 一方面,线程可能会在没有收到通知的情况下从等待中醒来,您根本无法假设有一个通知调用。

When a thread waits, it needs to do so in a loop, where in the test on the loop it checks some condition. 当线程等待时,它需要在循环中这样做,在循环测试中它会检查某些条件。 The other thread should set that condition variable so the first thread can check it. 另一个线程应设置该条件变量,以便第一个线程可以对其进行检查。 The recommendation that the Oracle tutorial makes is: Oracle教程提出的建议是:

Note: Always invoke wait inside a loop that tests for the condition being waited for. 注意:始终在循环中调用wait,以测试正在等待的条件。 Don't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true. 不要以为中断是针对您正在等待的特定条件,还是该条件仍然为真。

Other nitpicks: 其他nitpicks:

  • As your example is written, the JVM is not required to make the changes to your sum variable visible to the main thread. 在编写示例时,不需要JVM使对sum变量的更改对主线程可见。 If you add a synchronized instance method to access the sum variable, or access the sum within a synchronized block, then the main thread will be guaranteed to see the updated value of sum. 如果您添加了一个同步实例方法来访问sum变量,或者访问一个同步块中的sum,那么将保证主线程可以看到sum的更新值。

  • Looking at your logging, there is nothing SEVERE about an InterruptedException, it doesn't mean anything went wrong. 查看您的日志记录,发现InterruptedException异常严重,这并不意味着有任何错误。 An InterruptedException is caused when you call interrupt on a thread, setting its interrupt flag, and that thread is either currently waiting or sleeping, or enters a wait or sleep method with the flag still set. 当您在线程上调用中断,设置其中断标志,并且该线程当前正在等待或处于睡眠状态,或者进入一个仍设置了该标志的等待或睡眠方法时,就会导致InterruptedException。 In my toy example at the top of this answer I put the exception in the throws clause because I know it's not going to happen. 在此答案顶部的玩具示例中,我将异常放在throws子句中,因为我知道这种情况不会发生。

  • When the thread terminates it issues a notifyAll that anything waiting on that object will receive (again, that's how join is implemented). 当线程终止时,它发出notifyAll,所有在该对象上等待的内容都会收到(同样,这是实现联接的方式)。 It's better style to use Runnable instead of Thread, partly because of this. 最好使用Runnable而不是Thread,部分原因在于。

  • In this particular example it would make more sense to call Thread#join on the summing thread, rather than calling wait. 在此特定示例中,在求和线程上调用Thread#join比调用wait更有意义。

Here's the example re-written to use join instead: 这是改写为使用join的示例:

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 calls wait, locking on the thread object. Thread#join调用等待,锁定在线程对象上。 When the summing thread terminates it sends a notification and sets its isAlive flag to false. 求和线程终止时,它将发送通知,并将其isAlive标志设置为false。 Meanwhile in the join method, the main thread is waiting on the summing thread object, it receives the notification, checks the isAlive flag, and realizes it doesn't have to wait anymore, so it can leave the join method and print the result. 同时,在join方法中,主线程正在等待求和线程对象,它接收通知,检查isAlive标志,并意识到它不再需要等待,因此可以离开join方法并打印结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM