最近在读<<Java并发编程实践>>,在第二章中线程安全中降到线程锁的重进入(Reentrancy)
当一个线程请求其它的线程已经占有的锁时,请求线程将被阻塞。然而内部锁是可重进入的,因此线程在试图获得它自己占用的锁是,请求会成功。重进入意味着请求是基于“每一个线程”,而不是基于“每一次调用”(互斥锁是基于每次调用的)。重进入的实现是通过为每一个锁关联一个请求技术器和一个占有他的线程。当计数为0时,认为锁是未被占用的。线程请求一个未被占有的锁时候,JVM将记录锁的占有者,并且将请求计数设置为1。如果同一个线程再次请求这个锁,计数将递增;每次占用线程退出语句块时,计数器值将递减,直到计数器达到0时候,锁被释放。
重进入方便了锁行为的封装,因此简化了面向对象并发代码的开发。
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();//若内置锁是不可重入的,则发生死锁
}
}
在例子中,子类覆盖了父类的synchronized 类型的方法,并调用父类中的方法。如果没有可重入的锁,子类中可能就会产生死锁,因为Widget和LoggingWidget中的dosomething方法都是synchronized 类型的,都会在处理前试图获得Widget的锁。倘若内部锁不是可重入的,super.doSomething的调用者就永远无法获得Widget的锁。因为锁已经被占用,导致线程永久的延迟,等待着一个永远无法获得的锁。
以java上代码在同一个线程执行时,不会导致死锁,java中的synchronized 本身就是可以重入的(reentrant), 不管是synchronized方法,还是synchronized statements。参见:
http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
http://stackoverflow.com/questions/5787957/reentrant-synchronization-behavior-with-synchronized-statements
下面部分摘自论坛:
1、LoggingWidget 的对象调用doSomething方法时,锁对象为LoggingWidget对象 super.doSomething()调用是锁对象是LoggingWidget对象 运行程序,查看thread dump发现:调用super.doSomething()时锁对象依然是LoggingWidget对象。
"线程#1" prio=6 tid=0x0bd60400 nid=0x16f8 waiting on condition [0x0bf8f000..0x0bf8fd68]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Widget.doSomething(Widget.java:4)
- locked <0x03fbc150> (a LoggingWidget)
at LoggingWidget.doSomething(LoggingWidget.java:5)
- locked <0x03fbc150> (a LoggingWidget)
at LoggingWidget$1.run(LoggingWidget.java:15)
Locked ownable synchronizers:
- None
cectsky回复:
super.doSomething();子类会去get Widget的monitorlock,此时就会取到lock,如果monitorlock不可重入,就需要再次拿LoggingWidget的lock,but,此时LoggingWidget的lock已经被占用了,所以会发生deadlock。
支持可重入的话,只是简单的实现进入计数,每次进入+1,退出-1,如果计数器为0,则认为此时对象是没有被加锁 。
因为JVM的可重入解决了这个问题啊,所以dump里看到的是正确的流程。
2、在网上搜索发现,大伙都如是说: