this引用逃逸

来源:https://www.cnblogs.com/jian0110/archive/2018/07/26/9369096.html
-Advertisement-
Play Games

1、什麼是This逃逸? 在構造器構造還未徹底完成前(即實例初始化階段還未完成),將自身this引用向外拋出並被其他線程複製(訪問)了該引用,可能會問到該還未被初始化的變數,甚至可能會造成更大嚴重的問題。 廢話不多說,看一下代碼 輸出結果:這說明ThisEscape還未完成實例化,構造還未徹底結束。 ...


 

1、什麼是This逃逸?

  在構造器構造還未徹底完成前(即實例初始化階段還未完成),將自身this引用向外拋出並被其他線程複製(訪問)了該引用,可能會問到該還未被初始化的變數,甚至可能會造成更大嚴重的問題。

  廢話不多說,看一下代碼

 1 /**
 2  * 模擬this逃逸
 3  * @author Lijian
 4  *
 5  */
 6 public class ThisEscape {
 7     //final常量會保證在構造器內完成初始化(但是僅限於未發生this逃逸的情況下,具體可以看多線程對final保證可見性的實現)
 8     final int i;
 9     //儘管實例變數有初始值,但是還實例化完成
10     int j = 0;
11     static ThisEscape obj;
12     public ThisEscape() {
13         i=1;
14         j=1;
15         //將this逃逸拋出給線程B
16         obj = new ThisEscape();
17     }
18     public static void main(String[] args) {
19         //線程A:模擬構造器中this逃逸,將未構造完全對象引用拋出
20         /*Thread threadA = new Thread(new Runnable() {
21             @Override
22             public void run() {
23                 //obj = new ThisEscape();
24             }
25         });*/
26         //線程B:讀取對象引用,訪問i/j變數
27         Thread threadB = new Thread(new Runnable() {
28             @Override
29             public void run() {
30             31                 //可能會發生初始化失敗的情況解釋:實例變數i的初始化被重排序到構造器外,此時1還未被初始化
32                 ThisEscape objB = obj;
33                 try {
34                     System.out.println(objB.j);
35                 } catch (NullPointerException e) {
36                     System.out.println("發生空指針錯誤:普通變數j未被初始化");
37                 }
38                 try {
39                     System.out.println(objB.i);
40                 } catch (NullPointerException e) {
41                     System.out.println("發生空指針錯誤:final變數i未被初始化");
42                 }
43             }
44         });
45             //threadA.start();
46             threadB.start();
47     }
48 }

輸出結果:這說明ThisEscape還未完成實例化,構造還未徹底結束。

發生空指針錯誤:普通變數j未被初始化
發生空指針錯誤:final變數i未被初始化

另一種情況是利用線程A模擬this逃逸,但不一定會發生,線程A模擬構造器正在構造...而線程B嘗試訪問變數,這是因為

(1)由於JVM的指令重排序存在,實例變數i的初始化被安排到構造器外(final可見性保證是final變數規定在構造器中完成的);

(2)類似於this逃逸,線程A中構造器構造還未完全完成。

所以嘗試多次輸出(相信我一定會發生的,只是概率相對低),也會發生類似this引用逃逸的情況。

 1 /**
 2  * 模擬this逃逸
 3  * @author Lijian
 4  *
 5  */
 6 public class ThisEscape {
 7     //final常量會保證在構造器內完成初始化(但是僅限於未發送this逃逸的情況下)
 8     final int i;
 9     //儘管實例變數有初始值,但是還實例化完成
10     int j = 0;
11     static ThisEscape obj;
12     public ThisEscape() {
13         i=1;
14         j=1;
15         //obj = new ThisEscape();
16     }
17     public static void main(String[] args) {
18         //線程A:模擬構造器中this逃逸,將未構造完全對象引用拋出
19         Thread threadA = new Thread(new Runnable() {
20             @Override
21             public void run() {
22                 //構造初始化中...線程B可能獲取到還未被初始化完成的變數
23                 //類似於this逃逸,但並不定發生
24                 obj = new ThisEscape();
25             }
26         });
27         //線程B:讀取對象引用,訪問i/j變數
28         Thread threadB = new Thread(new Runnable() {
29             @Override
30             public void run() {
31                 //可能會發生初始化失敗的情況解釋:實例變數i的初始化被重排序到構造器外,此時1還未被初始化
32                 ThisEscape objB = obj;
33                 try {
34                     System.out.println(objB.j);
35                 } catch (NullPointerException e) {
36                     System.out.println("發生空指針錯誤:普通變數j未被初始化");
37                 }
38                 try {
39                     System.out.println(objB.i);
40                 } catch (NullPointerException e) {
41                     System.out.println("發生空指針錯誤:final變數i未被初始化");
42                 }
43             }
44         });
45             threadA.start();
46             threadB.start();
47     }
48 }

2、什麼情況下會This逃逸?

(1)在構造器中很明顯地拋出this引用提供其他線程使用(如上述的明顯將this拋出)。

(2)在構造器中內部類使用外部類情況:內部類訪問外部類是沒有任何條件的,也不要任何代價,也就造成了當外部類還未初始化完成的時候,內部類就嘗試獲取為初始化完成的變數

  • 在構造器中啟動線程:啟動的線程任務是內部類,在內部類中xxx.this訪問了外部類實例,就會發生訪問到還未初始化完成的變數
  • 在構造器中註冊事件,這是因為在構造器中監聽事件是有回調函數(可能訪問了操作了實例變數),而事件監聽一般都是非同步的。在還未初始化完成之前就可能發生回調訪問了未初始化的變數。

 

在構造器中啟動線程代碼實現:

 1 /**
 2  * 模擬this逃逸2:構造器中啟動線程
 3  * @author Lijian
 4  *
 5  */
 6 public class ThisEscape2 {
 7     final int i;
 8     int j;
 9     public ThisEscape2() {
10         i = 1;
11         j = 1;
12         new Thread(new RunablTest()).start();
13     }
14     //內部類實現Runnable:引用外部類
15     private class RunablTest implements Runnable{
16         @Override
17         public void run() {
18             try {
19                 System.out.println(ThisEscape2.this.j);
20             } catch (NullPointerException e) {
21                 System.out.println("發生空指針錯誤:普通變數j未被初始化");
22             }
23             try {
24                 System.out.println(ThisEscape2.this.i);
25             } catch (NullPointerException e) {
26                 System.out.println("發生空指針錯誤:final變數i未被初始化");
27             }
28         }
29         
30     }
31     public static void main(String[] args) {
32         new ThisEscape2();
33     }
34 }

 

構造器中註冊事件,引用網上的一段偽代碼將以解釋:

public class ThisEscape3 {
    private final int var;
 
    public ThisEscape3(EventSource source) {
     //註冊事件,會一直監聽,當發生事件e時,會執行回調函數doSomething source.registerListener(
       
//匿名內部類實現 new EventListener() { public void onEvent(Event e) {
            //此時ThisEscape3可能還未初始化完成,var可能還未被賦值,自然就發生嚴重錯誤 doSomething(e); } } );
var = 10; } // 在回調函數中訪問變數 int doSomething(Event e) { return var; } }

 

3、怎樣避免This逃逸?

  (1)單獨編寫一個啟動線程的方法,不要在構造器中啟動線程,嘗試在外部啟動。

...
private Thread t;
public ThisEscape2() {
    t = new Thread(new EscapeRunnable());
}

public void initStart() {
    t.start();
}
...

  (2)將事件監聽放置於構造器外,比如new Object()的時候就啟動事件監聽,但是在構造器內不能使用事件監聽,那可以在static{}中加事件監聽,這樣就跟構造器解耦了

static{
    source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        );
        var = 10;
    }
}    

4、總結

  this引用逃逸問題實則是Java多線程編程中需要註意的問題,引起逃逸的原因無非就是在多線程的編程中“濫用”引用(往往涉及構造器中顯式或隱式地濫用this引用),在使用到this引用的時候需要特別註意!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 做個記錄保存一下,以免以後再次用到忘記了。 沒有註釋應該沒事的吧!哈哈 ...
  • 主要是基於原生的JS對數組原型上的方法進行封裝、分析sort方法的功能、分不同情況進行討論。仿sort方法的功能實現對sort方法的封裝 ...
  • A代碼編輯器,線上模版編輯,仿開發工具編輯器,pdf線上預覽,文件轉換編碼B 集成代碼生成器 [正反雙向](單表、主表、明細表、樹形表,快速開發利器)+快速表單構建器freemaker模版技術 ,0個代碼不用寫,生成完整的一個模塊,帶頁面、建表sql腳本,處理類,service等完整模塊C 集成阿裡 ...
  • Python 熱度激增 根據 Stack Overflow 的一項調查顯示,Python 不僅在專業領域的使用率得到增長,在普通開發上的使用率也有所提升,有 40% 的受訪者表示他們現在正在使用 Python,有 25% 的受訪者表示希望學習 Python。 Codecademy 是一個編程語言使用 ...
  • 前陣子,Oracle 發佈了一個黑科技 "GraalVM",號稱是一個全新的通用全棧虛擬機,並具有高性能、跨語言交互等逆天特性,真有這麼神奇? GraalVM 簡介 GraalVM 是一個跨語言的通用虛擬機,不僅支持了 Java、Scala、Groovy、Kotlin 等基於 JVM 的語言,以及 ...
  • 題意 一家餐廳,第$i$天需要$r_i$塊餐巾,每天獲取餐巾有三種途徑 1、以$p$的費用買 2、以$f$的費用送到快洗部,併在$m$天後取出 3、以$s$的費用送到慢洗部,併在$n$天後取出 問滿足要求時的最小費用 Sol 一道非常不錯的網路流,應該不難看出是費用流。 首先進行拆點,把每個點早上和 ...
  • 上一篇博客Nginx配置詳解已經說過了nginx 的基本配置情況,今天來詳細講述一下nginx的location的配置原則, location是根據Uri來進行不同的定位,location可以把網站的不同部分,定位到不同的處理方式上, location的語法: location [=|~|~*|^~ ...
  • python_day_18 類: 新式類(python3x), 經典類(python2). 單繼承; 新式類經典類一樣. 多繼承: 新式類:廣度優先 類名.mro() 新式類. 經典類:深度優先. 定義一個Animal類,寫三個類:貓,狗,雞,,每個類中都有吃喝自己的方法 執行順序: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...