如果synchronized鎖住的對象被垃圾回收器回收了,會發生什麼呢?
下面代碼的目的是讓線程t1先進入synchronized(a)代碼塊,然後主線程調用full gc回收對象a,然後看看發生了什麼
public class Main {
//定義類A
public static class A{
int i = 3;
}
//等會要實驗的靜態引用a
public static A a = new A();
public static void main(String[] args) throws InterruptedException {
//實例化線程t1
Thread t1 = new Thread() {
@Override
public void run()
{
synchronized(a)
{
System.out.println("enter1");
//睡三秒好讓主線程gc
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show a value");
//因爲a已經被垃圾回收,看看是否報錯
System.out.println(a.i);
System.out.println("leave1");
}
}
};
t1.start();
//爲了先讓t1進如synchronized,所以讓主線程睡1s
Thread.sleep(1000);
//手動調用full gc 回收a
a=null;
System.gc();
System.out.println("set a = null and do a full GC");
//等待線程t1結束
t1.join();
System.out.println("finish");
}
}
程序結果
enter1
set a = null and do a full GC
show a value
Exception in thread "Thread-0" java.lang.NullPointerException
at normal.Main$1.run(Main.java:26)
finish
不出所料果然因爲a被回收,而訪問不了字段i!
再想想如果代碼塊鎖住的對象都被回收了,那代碼塊會不會在結束的時候報錯呢?
實驗如下
public class Main {
//定義類A
public static class A{}
//等會要實驗的靜態引用a
public static A a = new A();
public static void main(String[] args) throws InterruptedException {
//實例化線程t1
Thread t1 = new Thread() {
@Override
public void run()
{
synchronized(a)
{
System.out.println("enter1");
//睡三秒好讓主線程gc
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show a value");
//因爲a已經被垃圾回收,看看是否報錯
System.out.println("leave1");
}
}
};
t1.start();
//爲了先讓t1進如synchronized,所以讓主線程睡1s
Thread.sleep(1000);
//手動調用full gc 回收a
a=null;
System.gc();
System.out.println("set a = null and do a full GC");
//等待線程t1結束
t1.join();
System.out.println("finish");
}
}
實驗結果
enter1
set a = null and do a full GC
show a value
leave1
finish
並沒有報錯!!!
我們來分析線程中run函數的的字節碼指令
0: getstatic #15 // Field normal/Main.a:Lnormal/Mai
n$A;
3: dup
4: astore_1
5: monitorenter
6: getstatic #21 // Field java/lang/System.out:Ljav
a/io/PrintStream;
9: ldc #27 // String enter1
11: invokevirtual #29 // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
14: ldc2_w #35 // long 3000l
17: invokestatic #37 // Method java/lang/Thread.sleep:(
J)V
20: goto 28
23: astore_2
24: aload_2
25: invokevirtual #41 // Method java/lang/InterruptedExc
eption.printStackTrace:()V
28: getstatic #21 // Field java/lang/System.out:Ljav
a/io/PrintStream;
31: ldc #46 // String leave1
33: invokevirtual #29 // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
36: aload_1
37: monitorexit
我們知道monitorenter代表lock a,monitorexit代表unlock a(實際上是lock,unlock作用於操作棧頂,當然棧頂就是a),這說明什麼呢?
說明monitorexit 指令不需要對象a在內存中存活,即使對象a被垃圾回收了,這條指令不會報錯,只會當作什麼都沒發生
那麼monitorenter需不需要作用的對象存活呢?
把程序稍微改改就能驗證一下。我驗證出來的結果是——monitorenter必須保證作用的對象存活,否則會報出NullPointerException異常