並發基礎知識 — 線程安全性


  前段時間看完了《並發編程的藝術》,總感覺自己對於並發缺少一些整體的認識。今天借助《Java並發編程實踐》,從一些基本概念開始,重新整理一下自己學過並發編程。從並發基礎開始,深入進去,系統學習一下並發編程。

  編寫線程安全的代碼,核心在於要對狀態訪問操作進行管理,特別是對共享的(Shared)和可變的(Mutable)狀態的訪問。對象的狀態是指存儲在狀態變量(實例或靜態域)中的數據。對象的狀態還可能包括其他依賴對象的域。(Map.Entry)

  一個對象是否需要時線程安全的,取決於該對象是否被多線程訪問。這指的是程序中訪問對象的方式,而不是對象要實現的功能。要使得對象是線程安全的,要采用同步機制來協同對對象可變狀態的訪問。Java常用的同步機制是Synchronized還包括 volatile類型的變量,顯示鎖以及原子變量

  線程安全的程序是否完全由線程安全的類構成?答案是否定的,完全由線程安全的類構成的程序並不一定是線程安全的,線程安全類中也可以包含非線程安全的類。只有當類中僅包含自己的狀態時,線程安全類才有意義!

什么是線程安全性?

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

  正確性:某個類的行為與其規范相一致。(我理解的規范就是我們在編寫類時,能預知的狀態結果)

原子性

  競態條件(Race Condition):某個計算的正確性取決於多個線程的交替執行的時序。(線程的時序不同,產生的結果可能會不同)

  “先檢查后執行”,即通過一個可能失效的觀測結果來決定下一步的操作。

  首先觀察到某個條件為真,然后開始執行相關的程序,但是在多線程的運行環境中,條件判斷的結果以及開始執行程序中間,觀察結果可能變得無效(另外一個線程在此期間執行了相關的動作),從而導致無效。常見的就是(Lazy Singleton)

 1 /**
 2  * describe: 懶漢式單例模式
 3  *     優點:只有在需要使用LazySingleton1對象時,才真正生成一個LazySingleton1對象
 4  *     缺點:會因為某些Java 平台內存模型允許無序寫入,使得getInstance方法可能返回
 5  *           一個尚未執行構造函數的對象
 6  * Created by tianc on 2017/4/15.
 7  */
 8 public class LazySingleton {
 9     private static LazySingleton lazyInstance = null;
10     private LazySingleton() {
11     }
12     public static LazySingleton getInstance(){
13         if(lazyInstance == null){
14             synchronized (LazySingleton.class){
15                 if(lazyInstance == null){
16                     lazyInstance = new LazySingleton();
17                 }
18             }
19         }
20         return lazyInstance;
21     }
22 }
LazySingleton

 

  “讀取-修改-寫入”,基於對象之前的狀態來定義對象狀態的轉換。即使是volatile修飾的變量,在多線程的環境里面進行自增操作,同樣會發生競態條件,所以volatile不能保證絕對的線程安全(360面試問題)。

  引用書中定義:假定有兩個操作A和B,如果從執行A的線程來看,當另一個線程執行B時,要么將B完全執行完,要么完全不執行B,那么A和B對彼此來說是原子的。原子操作是指:對於訪問同一個狀態的所有操作(包括該操作本身)來說,這個操作是一個以原子方式執行的操作。

加鎖機制

  線程安全的定義中,多個線程間的操作無論采用何種執行時序或交替方式,都要保證不變性條件不被破壞。當不變性條件中涉及多個變量時,各個變量之間並不是互相獨立的,一個變量發生變化會對其他變量的值產生約束。因此,一個變量發生改變,在同一個原子操作里面,其他相關變量也要更新。

  內置鎖:同步代碼塊(Synchronized Block)包括兩部分:一個作為鎖的對象引用,一個作為由這個鎖保護的代碼塊。關鍵字Synchronized修飾方法就是一種同步代碼塊,鎖就是方法調用所在的對象,靜態的Synchronized方法以Class對象作為鎖。內置鎖或監視鎖就是以對象作為實現同步的鎖

  Java內置鎖,進入的唯一途徑是執行進入由鎖保護的同步代碼塊或方法。它相當於一種互斥鎖。

  重入鎖:當一個持有鎖的線程再次請求進入自己持有的鎖時,該請求會成功。"重入"意味着獲取鎖的操作的粒度是“線程”,而不是“調用”。重入的一種實現方式,為每個鎖關聯一個計數器和線程持有者。

用鎖來保護狀態

  由於鎖能使其保護的代碼路徑以串行形式訪問,因此可以通過鎖來構造一些協議以實現對共享狀態的獨占訪問。

  對象的內置鎖與其狀態之間沒有內在的聯系,雖然大多數類都將內置鎖用做一種有效的加鎖機制,但對象的域並不一定要通過內置鎖來保護。

  對於每個包含多個變量的不變性條件,其中涉及的所有變量都要使用同一個鎖來保護/同步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM