在看《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內存分區可能存在一些誤解,但願大佬能幫忙指導。