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

问题描述 投票:0回答:5

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

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

请澄清我有点困惑。

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

java multithreading concurrency locking wait
5个回答
180
投票

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

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

线程在进入同步方法时获取内在锁。 同步方法内的线程被设置为锁的所有者,并处于 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!"); }
    

23
投票
我准备了一个小测试类(一些非常脏的代码,抱歉)来演示 wait 实际上释放了锁。

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
    

9
投票

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
时的情况。被调用者线程不知道调用者线程(锁)
  将执行挂起的状态,因此已弃用。


3
投票
我认为应该在完整的背景下看待这一声明。

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

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

调用

synchronized

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


0
投票
这是书中的片段

Kathy Sierra 和 Bert Bates 的《Head First Java》

。记住这一点并研究一下原因

© www.soinside.com 2019 - 2024. All rights reserved.