我的印象是 wait() 释放所有锁,但我发现这篇文章说
“在同步方法中调用 wait 是获取内在锁的简单方法”
请澄清我有点困惑。
http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
“在同步方法中调用 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!");
}
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
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
时的情况。被调用者线程不知道调用者线程(锁) 将执行挂起的状态,因此已弃用。
当线程调用 d.wait 时,它必须拥有 d 的内在锁 — 否则会抛出错误。在同步中调用等待 方法是获取内在锁的简单方法。我知道他们应该将其简化为:
调用
synchronized
方法获取锁定 对象,我们可以简单地将wait()
调用放入synchronized
方法中。