Java:wait()是否释放同步块的locking
我的印象是wait()释放所有的锁,但我发现这个post说
“在同步方法内调用等待是获取固有locking的简单方法”
请澄清我有点困惑。
http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
“在同步方法内调用等待是获取固有locking的简单方法”
这句话是错误的,这是文档中的错误。
线程在进入同步方法时获取内部锁。 synchronized方法中的线程被设置为锁的所有者,并处于RUNNABLE状态。 任何尝试进入locking方法的线程都将变为BLOCKED状态 。
当线程调用等待时,释放当前的对象锁(它保留来自其他对象的所有锁)并进入WAITING状态。
当一些其他的线程调用通知或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!"); }
我准备了一个小的testing类(一些非常脏的代码,对不起)来certificate等待实际上释放了锁。
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
我认为应该从全面的angular度来看待这个说法。
当线程调用d.wait时,它必须拥有d的内部锁 – 否则会引发错误。 在同步方法内调用等待是获取内部locking的简单方法。
我明白,他们应该简化这样的:
synchronized
方法的调用获取locking对象,我们可以简单地在synchronized
方法中放置一个wait()
调用。
wait
::是java.lang.Object
类的一部分,所以我们只能在对象上调用这个方法。 调用这个需要监视(locking)该对象,否则IllegalMonitorStateException
将被抛出,例如)Thread.currentThread()。wait()将抛出此exception在下面的代码。Example1 public void doSomething() { Line 1 synchronized(lockObject) { //lock acquired Line 2 lockObject.wait(); // NOT Thread.currentThread().wait() Line 3 } }
现在在第3行调用等待将释放在第2行获取的锁。因此,进入第1行并等待获取
lockObject
锁的任何其他线程将获取该锁并继续。现在让我们考虑一下这个例子2; 这里只有
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
是为了避免线程的情况被挂起,当它处于挂起状态(未运行状态)的时候会导致死锁。 这对其他apis也是一样的行为。答案是暂停/恢复将在其他线程上执行,如
t1.suspend()
,因为这些API正在暂停Thread.currentThread()
。 因此,在调用这些API之前,用户必须注意不要锁住任何锁,以免造成死锁。 调用suspend
时情况并非如此。 被调用者线程不知道要执行暂停的调用者线程(locking)状态,因此不推荐使用。