Java:wait()是不是从同步块中释放锁

Posted

技术标签:

【中文标题】Java:wait()是不是从同步块中释放锁【英文标题】:Java : Does wait() release lock from synchronized blockJava:wait()是否从同步块中释放锁 【发布时间】:2012-10-26 08:02:46 【问题描述】:

我的印象是 wait() 释放所有锁,但我发现这篇文章说

“在同步方法中调用等待是获取内在锁的简单方法”

请澄清我有点困惑。

http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

【问题讨论】:

我认为你错了等待确实释放锁并暂停执行。它也写在下一行的同一个链接上。 是的,你是对的,“在第二个线程释放锁后的某个时间,第一个线程重新获取锁并通过调用等待返回来恢复。”。但最重要的是“等待告诉当前线程它应该停止,直到其他线程在同一对象上调用 notify(或 notifyAll)” wait 函数不会释放“所有锁”,但它会释放与调用wait 的对象关联的锁。 【参考方案1】:

“在同步方法中调用等待是获取内在锁的简单方法”

这句话是假的,是文档中的错误。

线程在进入同步方法时获取内在锁。 同步方法中的线程被设置为锁的所有者,并且处于RUNNABLE状态。 任何试图进入锁定方法的线程都会变成BLOCKED

当线程调用 wait 时,它会释放当前对象锁(它保留来自其他对象的所有锁),然后进入 WAITING 状态。

当其他线程在同一个对象上调用 notify 或 notifyAll 时,第一个线程将状态从 WAITING 更改为 BLOCKED, 通知线程不会自动重新获取锁或变为 RUNNABLE,实际上它必须与所有其他阻塞线程争夺锁。

WAITING 和 BLOCKED 状态都阻止线程运行,但它们有很大不同。

WAITING 线程必须通过来自其他线程的通知显式转换为 BLOCKED 线程。

WAITING 永远不会直接进入 RUNNABLE。

当 RUNNABLE 线程释放锁(通过离开监视器或等待)时,一个 BLOCKED 线程会自动取而代之。

总而言之,线程在进入同步方法或等待后重新进入同步方法时获取锁。

public synchronized guardedJoy() 
    // must get lock before entering here
    while(!joy) 
        try 
            wait(); // releases lock here
            // must regain the lock to reentering here
         catch (InterruptedException e) 
    
    System.out.println("Joy and efficiency have been achieved!");

【讨论】:

如果我有两个嵌套的同步块并调用wait()怎么办?支持吗? @serine,是的,它很容易导致死锁。查看this 答案以获取更多明确信息。 如果抛出InterruptedException,当前线程是否必须获取对象锁才能进入catch块? 假设只有一个线程并等待了一段时间,单位为毫秒;现在是否有可能一个线程可以直接从等待状态进入可运行状态?因为没有其他线程在这里锁定,因为只有单线程?我问这个是因为你告诉过“WAITING 永远不会直接进入 RUNNABLE”。 @cohadar : 应该是“事实上它必须与所有其他 WAITING 线程争夺锁”【参考方案2】:

我认为应该在完整的上下文中查看此声明。

当一个线程调用 d.wait 时,它必须拥有 d 的内在锁—— 否则抛出错误。在同步的内部调用等待 方法是获取内在锁的简单方法。

我知道他们应该将其简化为:

synchronized 方法的调用获得锁定 对象,我们可以简单地将wait() 调用放在synchronized 方法中。

【讨论】:

【参考方案3】:

我准备了一个小测试类(一些很脏的代码,抱歉)来演示等待实际上释放了锁。

public class Test 
    public static void main(String[] args) throws Exception 
        testCuncurrency();
    

    private static void testCuncurrency() throws InterruptedException 
        Object lock = new Object();
        Thread t1 = new Thread(new WaitTester(lock));
        Thread t2 = new Thread(new WaitTester(lock));
        t1.start();
        t2.start();
        Thread.sleep(15 * 1000);
        synchronized (lock) 
            System.out.println("Time: " + new Date().toString()+ ";" + "Notifying all");
            lock.notifyAll();
        
    

    private static class WaitTester implements Runnable 
        private Object lock;
        public WaitTester(Object lock) 
            this.lock = lock;
        

        @Override
        public void run() 
            try 
                synchronized (lock) 
                    System.out.println(getTimeAndThreadName() + ":only one thread can be in synchronized block");
                    Thread.sleep(5 * 1000);

                    System.out.println(getTimeAndThreadName() + ":thread goes into waiting state and releases the lock");
                    lock.wait();

                    System.out.println(getTimeAndThreadName() + ":thread is awake and have reacquired the lock");

                    System.out.println(getTimeAndThreadName() + ":syncronized block have finished");
                
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    
    private static String getTimeAndThreadName() 
        return "Time: " + new Date().toString() + ";" + Thread.currentThread().getName();
    

在我的机器上运行这个类会返回下一个结果:

Time: Tue Mar 29 09:16:37 EEST 2016;Thread-0:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-0:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-1:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:47 EEST 2016;Thread-1:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:52 EEST 2016;Notifying all
Time: Tue Mar 29 09:16:52 EEST 2016;Thread-1:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-1:syncronized block have finished
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-0:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:17:02 EEST 2016;Thread-0:syncronized block have finished

【讨论】:

非常好的例子哥们!【参考方案4】:

wait :: 是java.lang.Object 类的一部分,因此我们只能在对象上调用此方法。调用它需要对该对象进行监视器(锁定),否则 IllegalMonitorStateException 将被抛出,例如) Thread.currentThread().wait() 会在下面的代码中抛出这个异常。

   Example1
   public void doSomething()                                           Line 1
        synchronized(lockObject)  //lock acquired                      Line 2
            lockObject.wait();     // NOT Thread.currentThread().wait() Line 3
        
    

现在在第 3 行调用 wait 将释放在第 2 行获取的锁。因此进入第 1 行并等待获取lockObject 上的锁的任何其他线程将获取此锁并继续执行。

现在让我们考虑一下Example2;这里只有lockObject2 锁被释放,当前线程仍然持有lockObject1 锁。这会导致死锁;所以用户在这种情况下应该更加小心。

   Example2 
        public void doSomething()                                      Line 1
             synchronized(lockObject1)  //lock1 acquired               Line 2
                 synchronized(lockObject2)  //lock2 acquired           Line 3
                     lockObject2.wait();                                Line 4
                 
             
        

如果这个等待被替换为sleep, yield, or join 他们没有 解除锁定的能力。只有等待才能释放它持有的锁。

请注意t1.sleep()/t1.yield() 静态 api 的位置并始终 操作将在 currentThread 上执行,而不是在线程 t1 上。

那么让我们了解一下suspend和这些api的sleep, yield, join有什么区别;因为suspend 已被弃用以避免线程持有锁的情况,当它处于挂起(非运行状态)未定义的时间时会导致死锁。这对于其他 api 也是相同的行为。

答案是暂停/恢复将在其他线程上执行,例如t1.suspend(),因为这些 api 正在暂停 Thread.currentThread()。 因此,用户在调用这些 api 之前注意不要持有任何锁以避免死锁。这不是 调用suspend 时的情况。被调用者线程不知道调用者线程(锁) 它将执行挂起的状态,因此已弃用。

【讨论】:

以上是关于Java:wait()是不是从同步块中释放锁的主要内容,如果未能解决你的问题,请参考以下文章

Java中的sleep()和wait()异同

为什么 wait(), notify()和 notifyAll ()必须在同步方法或 者同步块中被调用?

为什么 wait(), notify()和 notifyAll ()必须在同步方法或 者同步块中被调用?

为什么wait和notify方法要在同步块中调用?

面试官:为什么wait(),notify()必须在同步方法/代码块中调用?

面试官:为什么wait(),notify()必须在同步方法/代码块中调用?