最近在学习java的并发,在看资料介绍java并发的可重入性时举了个例子,代码如下 :
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
} 我的问题是:LoggingWidget中的super.doSomething()这个super到底是如何调用到父类的方法的,是类似“宏替换”(就是把父类的方法加到子类的这个位置,然后在编译运行),还是如何?
这里父类子类的方法都有synchronized同步,当调用子类LoggingWidget的doSomething()时锁对象肯定是当时调用的那个LoggingWidget实例,可是问题是当执行到super.doSomething()时,要调用父类的同步方法,那此时锁对象是谁?还是LoggingWidget实例?还是什么?
谢谢!
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
} 我的问题是:LoggingWidget中的super.doSomething()这个super到底是如何调用到父类的方法的,是类似“宏替换”(就是把父类的方法加到子类的这个位置,然后在编译运行),还是如何?
这里父类子类的方法都有synchronized同步,当调用子类LoggingWidget的doSomething()时锁对象肯定是当时调用的那个LoggingWidget实例,可是问题是当执行到super.doSomething()时,要调用父类的同步方法,那此时锁对象是谁?还是LoggingWidget实例?还是什么?
谢谢!
super.doSomething()编译器就知道你是调用父类的方法了。
因此,调用super.doSomething(),就是锁住了隐含的父类对象。
我认为这并不是对同一个锁进入了2次,而是获得了2个不同的锁:子类对象的锁和父类对象的锁
如果super锁住了父类对象,那么另一个线程仍然可以获得子类对象的锁。按照这个假设,以下程序应该输出
something sleepy!
something else
woke up!但输出的是
something sleepy!
woke up!
something else所以获得的锁是同一个。或者说,不关JVM内部是怎么处理的,对于程序员来说都无关紧要,一个对象只有一个内部锁。public class Test {
public static void main(String[] args) throws InterruptedException {
final TestChild t = new TestChild(); new Thread(new Runnable() {
@Override
public void run() {
t.doSomething();
}
}).start();
Thread.sleep(100);
t.doSomethingElse();
} public synchronized void doSomething() {
System.out.println("something sleepy!");
try {
Thread.sleep(1000);
System.out.println("woke up!");
}
catch (InterruptedException e) {
e.printStackTrace();
}
} private static class TestChild extends Test {
public void doSomething() {
super.doSomething();
} public synchronized void doSomethingElse() {
System.out.println("something else");
}
}
}
woke up!
something else这个结果说明不了任何问题,我帮你分析一下为什么产生这个结果
你这个程序有2个线程,main线程和 new Thread线程
你首先执行的是new Thread线程的t.doSomething();方法
这个方法首先获得子对象的锁,再获得父对象的锁,在父对象方法中打印something sleepy
然后睡1秒,在睡觉过程中,他持有2个锁,子对象的锁和父对象的锁
此时调度器选中main线程,main线程调用t.doSomethingElse();
执行此方法需要子对象的锁,但很遗憾,子对象的锁目前仍然在new Thread手里,所以main线程阻塞
然后new Thread睡醒了,打印woke up!
然后释放父对象的锁
然后释放子对象的锁
然后main线程不在阻塞,执行t.doSomethingElse();方法,打印something else
每次调用这个函数,就会获得一把锁,这个锁就是函数所在的类的对象。public class LoggingWidget extends Widget {
public synchronized void doSomething() --这里的锁是子类对象{
System.out.println(toString() + ": calling doSomething");
super.doSomething(); --这里的锁是父类对象
}
}
确实,我没看仔细
如此看来,结论是这样的
在子类中,调用父类的synchronized方法,锁住的是子类的对象,而不是父类的对象
确实,我没看仔细
如此看来,结论是这样的
在子类中,调用父类的synchronized方法,锁住的是子类的对象,而不是父类的对象这应该是个比较基础的问题啊,super这个东西的含义是什么?不是指的父类,而是:一个用来引用继承而来的成员的引用。
那么super.doSomething()的含义是,通过super引用调用从父类继承而来的doSomething()方法,那么明显锁的还是当前的子类对象
这种认为是不正确的,实际上是锁应该是同一个对象,如果是不同对象的话就这里就不会存在死锁的风险,而在资料举这个例子的时候是为了解释由于java的可重入性才解决死锁的风险
你的这个想法非常好,虽然我也没有试过,不过从原理上解释了我的疑问,同步方法是同步块(this)的简写形式,而如果使用this的话,不管是调用LoggingWidget.doSomething(),还是在这个方法里面调用父类的doSomething(),这个this都代表当前对象,所以是同一个,且都是LoggingWidget对象也感谢大家的回答,如果没有其他问题的话,这周我就结贴了,如果发现我上面的说法有问题,欢迎指正.