71

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

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

请澄清我有点困惑。

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

4

4 回答 4

167

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

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

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

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

当其他一些线程在同一个对象上调用 notify 或 notifyAll 时,第一个线程将状态从 WAITING 更改为 BLOCKED,Notified 线程不会自动重新获取锁或变为 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!");
}
于 2012-12-01T21:51:28.133 回答
23

我准备了一个小测试类(一些非常脏的代码,对不起)来证明等待实际上释放了锁。

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
于 2016-03-29T06:19:16.390 回答
8

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。被调用者线程不知道它将执行挂起的调用者线程(锁定)状态,因此不推荐使用。

于 2016-06-15T06:34:10.497 回答
2

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

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

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

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

于 2015-11-18T19:43:24.843 回答