在看《java并发编程实战》时,讲可重入锁时,子类改写父类的synchronized方法,而后调用父类中的synchronized方法,若是内置锁不是可重入的将致使死锁。java
public class Widget{
public synchronized void doSomething(){
···
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
···
}
}
复制代码
咱们知道synchronized修饰实例方法,锁对象是this对象。子类中的synchronized应该锁住的是子类对象,父类中的synchronized方法应该锁住的是父类对象。两个方法的锁对象根本不一样,因此根本不须要可重入锁。那为何这里说须要可重入锁?
1.书中写错了
2.其实子类和父类的锁对象为同一个,那么究竟是子类对象仍是父类对象?express
因为synchronized修饰实例方法,锁对象是当前实例对象,咱们分别在父类和子类中打印一下this对象,看一下它们到底是什么。编程
public class Father{
public synchronized void test(){
System.out.println("father'this="+this);
System.out.println("father'super"+super.toString());
}
}
public class Son extends Father{
@override
public synchronized void test(){
System.out.println("son's this="+this);
System.out.println("son's super="+super.toString());
super.test();
}
}
public static void main(String[] args){
Son son = new Son();
son.test();
}
//结果
son's this=Son@39b43cbc son's super=Son@39b43cbc
father's this=Son@39b43cbc father's super=Son@39b43cbc
复制代码
咱们能够看到当用子类调用父类方法时,子类父类的this都是指向同一个引用那么子类和父类的锁对象为同一个,而且为子类对象。 咱们用实例验证一下,bash
//改进一下代码
public class Father{
public synchronized void test(){
System.out.println("father test");
}
public synchronized void test2(){
System.out.println("father test2");
while(true);
}
}
public class Son extends Father{
@override
public synchronized void test(){
System.out.println("son test");
}
@override
public void test2(){
super.test2();
}
}
public static void main(String[] args) {
Son son = new Son();
Thread thread1= new Thread(()->{
son.test2();
});
Thread thread2 = new Thread(()->{
son.test();
});
thread1.start();
try {
Thread.sleep(1000);//这里可能休眠的时线程1,可是不重要,只要线程1能先启动,得到锁就好
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
//结果
son test2
father test2
//由于thread1中son经过没有被synchronized修饰的test2()调用父类被synchronized修饰的test2()方法
,若是父类synchronized获取的锁不是和子类不是同一个,那么,thread2经过son调用被synchronized修饰
的test()方法,必然可以进入该方法,由于锁对象不是同一个,然而根据验证结果,锁对象为同一个,而且为子类对象。
复制代码
在上边的实验中,咱们看到this和super都指向同一个引用。咱们一般理解的是this指向的是本实例对象,super是父类实例对象的一个引用。然而为何this和super指向了同一个引用?并发
//对于this,jls中这样描述
When used as a primary expression, the keyword this denotes a value that is a reference to the object
for which the instance method or default method was invoked (§15.12), or to the object being constructed.
The value denoted by this in a lambda body is the same as the value denoted by this in the surrounding context.
//对于super的一些描述
The form super.Identifier refers to the field named Identifier of the current object, but with the current
object viewed as an instance of the superclass of the current class.
复制代码
能够看出,this表示一个指向调用当前实例方法的那个对象的引用,而super上边的解释并不明显,咱们再看一个其它的解释app
The usage of the super reference when applied to overridden methods of a superclass is special;
it tells the method resolution system to stop the dynamic method search at the superclass,
instead of at the most derived class (as it otherwise does).
复制代码
就是说super具备阻止动态调用的过程。 而这也是为何有了this关键后,还有super关键字,而且super和this 指向同一个引用。 经过实例来看一下jvm
public class Son extends Father{
public void test(){
this.test2();
super.test2();
}
}
public class Father{
public void test(){
}
public void test2(){
System.out.println("father test2");
}
}
main()方法:
Son son = new Son();
son.test2();
//结果
father test2
father test2
//字节码
public void test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method test2:()V
4: aload_0
5: invokespecial #3 // Method Father.test2:()V
8: return
LineNumberTable:
line 4: 0
line 5: 4
line 6: 8
}
复制代码
咱们能够看到经过this,super均可以调用父类中的方法或者成员变量
由于对象在堆中保存的实例数据包含了它继承过来的实例数据,因此子类对象中保存了父类的信息,之前的对super的理解“指向父类对象的一个引用”实际上是错误的,这里根本就没有父类对象,所谓的父类对象,实际上是子类实例数据中继承过来的数据。也就是说在堆中只有一个对象,并无父类对象。
ide
因此super和this只能指向一个对象,那就是子类对象(其实应该是表示一个指向调用当前实例方法的那个对象的引用)。
那么既然子类已经有父类的实例数据,直接经过this调用便可,那么为何还要super呢,而且super和this指向的都是同一个对象?
仔细观察上边的字节码就会发现,ui
this.test2()->invokevirtual
super.test2()->invokespecial
复制代码
this是动态调用的,supers是在编译期就已经知道的调用哪个方法。
因此this,super的目的就是:防止存在方法重写时的调用混乱,this关键字调用方法要经历一个动态分析过程,而super关键字调用的变量或方法是肯定的,也就是继承过来的实例数据中的父类的成员变量或成员方法this
了解jvm都知道方法中的第一个参数实际是this,咱们经过子类调用父类方法,传进去的this实际是子类的this
//jvms中的解释
Note that methods called using the invokespecial instruction always pass this to the invoked method as its first argument. As usual, it is received in local variable 0.
复制代码
//伪代码
public class Father{
public void test(){
}
}
public Class Son extends Father{
public void test(){
//实际上test参数中的第一个为this,jvm自动帮咱们加入的(我这里显式的写上),因此在父类test方法中获取的是同一个this
super.test(this);
}
}
复制代码
那么问题来了,super和this极其类似,那么jvm是否在方法的参数列表中也自动的添加了super呢?
答案是否认的
public void test2(){
}
//字节码
public void test2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
//这里 locals=1,说明只有一个变量那就是this
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 5: 0
}
复制代码
在jsl看到的一个例子
public class son extends Father{
int a=10;
@Override
public void test() {
System.out.println("son test");
System.out.println("super.a="+super.a);
System.out.println("((Father)this).a="+((Father)this).a);
super.test2();
((Father)this).test2();
}
@Override
public void test2() {
System.out.println("son test2");
}
}
public class Father{
int a=20;
public void test(){
System.out.println("father test");
}
public void test2(){
System.out.println("father test2");
}
}
main():
new Son().test();
\\结果
son test
super.a=20
((Father)this).a=20
father test2
son test2
复制代码
这里实际上是一个动态绑定的结果。
动态绑定的关键点:
子类在方法区维护了一个虚方法表(Vtable,在链接阶段完成),全部对象共享一个vtable
1.若是子类没有重写父类的方法,子vtable直接指向父vtable
2.若是子类重写了父类的方法,子vtable和父vtable索引相同方便查找
复制代码
虚方法表的使用,每一个对象就不须要额外的指针空间来保存对每一个方法的引用,提升了效率。
以上的分析有的部分为我的根据字节码的理解,对jvm内存分区可能存在一些误解,但愿大佬能帮忙指导。