等待()从同步块释放锁

我的印象是 wait ()会释放所有的锁,但是我发现这个帖子说

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

请澄清一下,我有点糊涂了。

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

56556 次浏览

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

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

线程在 进入同步方法时获得内部锁。 将同步方法中的线程设置为锁的所有者,并且处于 可以跑状态。 任何尝试进入锁定方法的线程都成为 被屏蔽了

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

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

等待和阻塞状态都阻止线程运行,但它们是非常不同的。

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

等待永远不会直接进入 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!");
}

我认为这个声明应该放在整个背景下来看待。

当一个线程调用 d.wait 时,它必须拥有 d ー的内部锁 否则将引发错误。在同步的 方法是获取内部锁的一种简单方法。

我理解他们应该简化成这样:

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

我准备了一个小的测试类(一些非常脏的代码,抱歉)来演示 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

java.lang.Object类的一部分,所以我们只能在对象上调用这个方法。调用此对象需要对该对象进行监视(锁定) ,否则 将抛出 IllegalMonitorStateException,例如) 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 之前要注意不要持有任何锁,以避免死锁。这不是 被调用线程不知道调用线程(锁) 要执行挂起的状态,因此不推荐。