日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java并发编程一:基础知识

發布時間:2023/12/15 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java并发编程一:基础知识 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、線程安全性

1.1 什么是線程安全性?

當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或者協同,這個類都能表現出正確行為。

1.2 原子性

i++ -> "讀取-修改-寫入"

1.2.1 競態條件 和 復合操作

當某個計算的正確性取決于多個線程的交替執行時序時,那么就會發生競態條件。或者說,之所以發生競態條件是因為操作是非原子性而是一個復合操作。

public class A {private static A instance = null;public static A getInstance(){if(instance == null){instance = new A();}return instance;} }復制代碼

這里會存在競態條件(先檢查后執行),假設線程A1和A2同時執行getInstance方法,A1看到instance實例為空,它會去new A();A2也要判斷instance是否為空,此時的instance是否為空取決于不可預測的時序(包括線程調度方式),以及A1要花多長時間new A 實例;如果A1在new操作時,輪到A2線程被調度,那么此時A2判斷instance也為空,最終會出現兩個A實例。 同理i++也存在這樣的競態條件(讀取-修改-寫入) 解決:避免某個線程修改變量時,通過某種方式防止其他線程使用這個變量,即保證操作是原子方式執行。

先檢查后執行 -- 加鎖實現同步
i++ -- concurrent.atomic 實現原子操作

1.3 加鎖機制

1.3.1 內置鎖

Synchronized

鎖重入

“重入”意味著獲取鎖的操作粒度是“線程”,而不是“調用”,避免死鎖。

二、共享對象

2.1 可見性

當讀操作和寫操作在不同的線程中執行時,無法確保執行讀操作的線程能看到其它線程寫入的值。

問題:

  • 數據失效
  • public class ClassA {private int value;public int getValue() {return value;}public void setValue(int value) {this.value = value;} } 復制代碼

    在沒有同步的情況下get和set訪問value,數據失效很容易出現:如果某個線程調用了set,那么另一個在調用get的線程可能會看到更新后的value值,也可能看不到。

  • 非原子的64位操作
    非volatile類型的64位數值變量long和double,JVM允許將64位的讀操作或者寫操作分解為兩個32位的操作,當讀取一個非volatile類型的long變量時,如果對該變量的讀操作和寫操作在不同的線程中執行,那么很可能讀到某個值得高32位和另一個值得低32位,造成數據失效。
  • 解決:

  • 加鎖與可見性
    加鎖不僅僅具有互斥性和包括可見性,為了確保所有線程都能看到共享變量的最新值,所有執行讀操作或者寫操作的線程都必須在同一個鎖上同步。
  • volatile 變量
    可見性與重排序 使用場景:確保自身狀態可見,確保引用對象狀態可見,標記重要程序生命周期事件發生(初始化和關閉) 某操作完成、中斷或者狀態標志
  • volatile boolean asleep; ... while(!asleep) {countSomeSheep(); } 復制代碼

    2.2 發布與逸出

    1. 發布: 發布一個對象的意思是,使對象能夠在當前作用域之外的代碼中使用。

  • 將一個引用存儲到其他代碼可以訪問的地方;
  • 在一個非私有的方法中返回該引用;
  • 將該對象傳遞到其他類的方法中等。
  • public static Set secrets; public void init(){secrets = new HashSet(); } 復制代碼

    當發布某個對象時,可能間接地發布其他對象。例如如果將一個Secret對象添加到集合secrets中,那么在發布secrets的同時,也會發布Secret對象,因為任何代碼都可以遍歷這個集合,并獲得對Secret對象的引用。

    2. 逸出: 當某個不應該發布的對象被發布時,這種情況就是逸出。
    對象逸出會導致對象的內部狀態被暴露,可能危及到封裝性,使程序難以維持穩定;若發布尚未構造完成的對象,可能危及線程安全問題。
    最常見的逸出是this引用在構造時逸出,導致this引用逸出的常見錯誤有:

  • 在構造函數中啟動線程:
    當對象在構造函數中顯式還是隱式創建線程時,this引用幾乎總是被新線程共享,于是新的線程在所屬對象完成構造之前就能看見它。 避免構造函數中啟動線程引起的this引用逸出的方法是不要在構造函數中啟動新線程,取而代之的是在其他初始化或啟動方法中啟動對象擁有的線程。
  • 在構造方法中調用可覆蓋的實例方法:
    在構造方法中調用那些既不是private也不是final的可被子類覆蓋的實例方法時,同樣導致this引用逸出。 避免此類錯誤的方法時千萬不要在父類構造方法中調用被子類覆蓋的方法。
  • 在構造方法中創建內部類:
    在構造方法中創建內部類實例時,內部類的實例包含了對封裝實例的隱含引用(深入理解 內部類),可能導致隱式this逸出。例子如下:
  • 不要在構造函數中使用this引用逸出,也不要在構造方法中調用可改寫“實例”的方法;

    public class SafeListener { private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } ); } public static SafeListener newInstance(EventSource source) { SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } } 復制代碼

    2.3 線程封閉

    當訪問共享的可變數據時,通常需要同步。一種避免使用同步的方式就是不同享數據,這叫做線程封閉。java提供了一些機制來維持線程封閉性,例如局部變量和ThreadLocal類。 線程封閉技術的一個常見應用是JDBC的Connection對象。JDBC規范不要求Connection對象時線程安全的,而要求連接池是線程安全的。線程通過線程池中獲得一個Connection對象,并且用該對象來處理請求,使用完之后再返回給連接池。由于大多數請求(例如Servlet請求和EJB)都是單個線程采用同步的方式來處理,并且在Connection對象返回前,連接池不會再把它分配給其它線程,因此這種連接在處理請求時,把Connection對象封閉在線程中。

    2.3.1 棧封閉

    將變量封閉在方法中

    2.3.2 threadLocal 為每個使用變量的線程都存有一個副本

    使用場景:

  • 一個單線程應用程序移植到多線程環境,將共享的全局變量轉換為threadLocdal對象
  • 避免調用每個方法都要傳遞上下文信息
  • 2.4 不變性

    如果一個對象在創建后其狀態就不能被修改,那么這個對象就稱為不可變對象。 不可變對象需要滿足下面條件:

  • 對象本身是final的(避免被子類化),聲明屬性為private 和 final
  • 不可變對象的狀態在創建后就不能再改變,不要提供任何可以修改對象狀態的方法 - 不僅僅是set方法, 還有任何其它可以改變狀態的方法,每次對他們的改變都是產生了新的不可變對象的對象。
  • 不可變對象能被正確地創建(在創建過程中沒有發生this引用逸出)。
  • 如果類有任何可變對象屬性, 那么當它們在類和類的調用者間傳遞的時候必須被保護性拷貝
  • 不可變的對象一定是線程安全的

    2.5 安全發布

    轉載于:https://juejin.im/post/5c348d39f265da616f7024b2

    總結

    以上是生活随笔為你收集整理的java并发编程一:基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。