最近又开始咀嚼以前狼吞虎咽过的《java并发编程实践》,看到一段突然理解不了,发上来大家瞅瞅
是关于sychronized可重入的。
引用
当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞。然而内部锁是可重入的,因此线程在试图获得它它自己占有的锁时,请求会成功。重进入意味着所有的请求是基于“每线程(per-thread)”的,而不是基于“每调用(per-invocation)”的。重进入的
实现是通过为每个锁关联一个请求计数和一个占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM将记录锁的占有者,且将请求计数置为1.如果同一线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。直到
计数器达到0时,锁被释放。
重进入方便了锁行为的封装,因此简化了面向对象并发代码的开发。如下所示,子类覆盖了父类sychronized类型的方法,并调用父类中的方法。如果没有可重入的锁,这段看上去很自然的代码就会产生死锁。因为Widget和LogginWidget中的doSomething方法都是
sychronized类型的,都会在处理前试图获得Widget的锁,因为锁已经被占有,导致线程会永久地延迟,等待着一个永远无法获得的锁。重进入帮助我们避免了这种死锁。
public class Widget{
public sychronized void doSomething(){
...
}
}
public class LogginWidget extends Widget{
public sychronized void doSomething(){
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
而书中前部分说到:
引用
Java提供了强制原子性的内置锁机制:sychronized块。一个sychronized块有两部分:锁对象引用,以及这个锁保护的代码块。sychronized方法是对跨越了整个方法体的sychronized块的简短描述,至于sychronized方法的锁,就是该方法所在的对象本身。(静
态的sychronized方法从Class对象上获取锁)
让我产生了一些想法:
引用
因为Widget和LogginWidget中的doSomething方法都是sychronized类型的,都会在处理前试图获得Widget的锁
这里只有LogginWidget这个实例,而父类中的锁定实际上应该是锁定到子类中。为何文中说会获得Widget的锁,有点耐人寻味?
public class Widget{
public synchronized static void doSomething(){
...
}
}
public class LogginWidget extends Widget{
//不覆盖
public sychronized void doSomething2(){
System.out.println(toString() + ": calling doSomething");
doSomething();
}
}
此处LogginWidget实例doSomething2方法的获得锁是怎么样的?
super.doSomething()这句的锁对象是谁?
Widget的Class作为锁对象还是LogginWidget的Class作为了锁对象?
根据类似方法的绑定策略:
static方法有父类静态绑定,也就是说子类无法覆盖父类的静态方法。
实例方法则在子类中动态绑定,也就是子类可以覆盖父类的实例方法。
那么是否这个对象锁应该是Widget的Class对象呢?那是不是doSomething2的方法的执行会锁定两个对象呢,一个是Widget的Class一个是LogginWidget自身实例。
为此做了一个试验:
public class Sync {
public synchronized static void doSomething(String name) throws InterruptedException{
System.out.println(name + " super.doSomething, will sleep 10s");
Thread.sleep(10000);
System.out.println(name + " wake up");
}
public synchronized static void test(String name){
System.out.println(name + " super.test");
}
public static void main(String[] args) throws InterruptedException{
final SubSync sub = new SubSync();
Thread t1 = new Thread(new Task(sub,"t1"));
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
Sync.doSomething("t2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
class Task implements Runnable{
private SubSync sync;
private String name;
public Task(SubSync sync,String name){
this.sync = sync;
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name + " stated.");
sync.doSomething2(name);
System.out.println(name + " finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class SubSync extends Sync{
public synchronized void doSomething2(String name) throws InterruptedException{
System.out.println(name + " sub.doSomething2");
doSomething(name);
}
}
输出结果为:
t1 stated.
t1 sub.doSomething2
t1 super.doSomething, will sleep 10s
t2 begin
t1 wake up
t1 finished.
t2 super.test
静态方法是锁住了Sync类的Class实例。so,SubSync方法doSomething2执行时实际上是锁定了两个对象,一个是Sync的Class,一个是SubSync实例本身!
那这样死锁的机会就会增加很多。
因为同时要锁住两个对象,就存在着相互等待的情况,有可能一个线程持有了一个同步方法中的其中一个锁,而另一个线程持有了该同步方法中的另一个锁,两个线程相互等待对方释放锁以便自己获得。