1. 程式人生 > >JavaSE多執行緒

JavaSE多執行緒

鎖的基礎知識:

鎖的型別:

悲觀鎖和樂觀鎖

樂觀鎖:認為讀多寫少,遇到併發寫的可能性極低,即每次去拿資料的時候認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷在此期間有沒有                人更新這個資料。

              判斷依據:在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣就更新),如果失敗就重複 讀 ->  比較 ->寫的操作

             Java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗

CAS:

        簡單的來說,CAS有三個運算元,記憶體值 V, 舊的預期值 A,要修改的新值B。當且僅當預期值A與記憶體值V相等時,將記憶體值修改為B,否則返回V。

        這是一種樂觀鎖的思路。它相信在它之前沒有執行緒去修改記憶體值

        缺點:會發生 ABA問題,即A被修改成B,然後又被修改成A,不能感知到修改

悲觀鎖:認為寫多讀少,遇到併發寫的可能性高,每次在讀寫資料的時候都會上鎖,如果別的執行緒想讀寫這個資料就會block直到拿到鎖

1.Synchronized底層實現(*****):

Java物件頭:鎖的物件儲存在物件頭中,synchronized鎖的是物件

鎖的狀態分為:自旋鎖,偏向鎖,輕量級鎖,重量級鎖)

自旋鎖:加鎖後,只有一個執行緒進入程式碼塊,其他執行緒等待(自旋等待),為重量級鎖(monitorenter,重量級鎖標誌)

自旋鎖:1.相當於怠速停車,具有不公平性,處於自旋狀態的鎖比重量級鎖(它處於阻塞狀態)更容易獲得鎖

              2.自旋鎖不會引起呼叫者立即睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者不放棄處理器的執行時間,進行忙迴圈(自旋),

                 跑的是無用的執行緒,JDK1.6後預設開啟了自旋鎖,自旋次數預設10次

              3.自旋鎖一直佔用CPU,在未獲得鎖的情況下,一直進行執行 - - - 自旋,若不能在很短的時間內獲得鎖,將會使CPU效率降低

自適應自旋:1.減少無用執行緒佔用CPU的問題

                     2.自旋的時間不再是固定的,由前一個在同一個鎖上的自旋時間及鎖擁有者的狀態來決定。

                     3.如果在同一個鎖物件上,自選等待剛好成功獲得鎖,並且持有鎖的執行緒正在執行中,那麼虛擬機器就認為這次自旋很有可能會獲得鎖,

                        將會允許自旋等待更長的時間

重量級鎖(Java頭中的monitorenter物件):os需要從使用者態 -> 核心態,開銷較大。同一時刻多個執行緒競爭資源

輕量級鎖(CAS操作):多執行緒在不同時刻訪問共享資源,樂觀鎖的一種

偏向鎖:更為樂觀的鎖,假定從始至終都是同一個執行緒在訪問共享資源

JDK中鎖只有升級過程,沒有降級過程。  無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖

鎖消除:消除類似於Vector等執行緒安全集合不存在共享資源競爭時,JVM將程式碼優化,解鎖

2.synchronized 與 ReentrantLock 區別

synchronized:

synchronized是Java中最基本同步互斥的手段,可以修飾程式碼塊,方法,類

在修飾程式碼塊的時候需要一個reference物件作為鎖的物件

在修飾方法的時候預設當前物件作為鎖的物件

修飾類的時候預設當前類的class物件作為鎖的物件

synchronized會在進入同步塊的前後分別形成monitorenter和monitorexit位元組碼指令,在執行monitorenter指令時會嘗試獲取物件的鎖,如果

 此物件沒有被鎖,或此物件已被當前執行緒鎖住,則鎖的計數器+1,如果monitorexit被鎖的物件的計數器-1,直到為0就釋放該物件的鎖,由此

synchronized是可重入的,不會將自己鎖死。

ReentrantLock:

除了synchronized的功能,多了三個高階功能

1.等待可中斷  2.公平鎖  3.繫結多個Condition

1.等待可中斷

    在持有鎖的執行緒長時間不釋放鎖的時候,等待的執行緒可以放棄等待. tryLock(long timeout,TimeUnit unit)

2.公平鎖

    按照申請鎖的順序來一次獲得鎖稱為公平鎖,synchronized的是非公平鎖,ReentrantLock可以通過建構函式實現公平鎖

     new ReentrantLock(boolean fair)

3.繫結多個Condition

    通過多次newCondition可以獲得多個Condition物件,可以簡單的實現比較複雜的執行緒同步的功能

總的來說,lock更加靈活

二者相同點: Lock能完成synchronized所實現的所有功能

3.Lock(AQS: AbstractQueuedSynchronized 核心概念:一個是表示(鎖)狀態的變數、一個是佇列)

4.Lock與Synchronized的區別

   a.synchronized內建關鍵字,在JVM層面實現,發生異常時,會自動釋放執行緒佔有的鎖,因此不會發生死鎖現象。

     Lock在發生異常時,如果沒有主動通過unLock去釋放鎖,很可能產生死鎖現象,因此使用Lock時需要在finally塊中釋放鎖

  b. Lock具有高階特性: 時間鎖等候,可中斷鎖等候

  c.當競爭資源非常激烈時(即有大量執行緒同時競爭時),此時Lock的效能要遠遠優於synchronized

5.執行緒池

JDK內建的四大執行緒池:

 1.建立無大小限制的執行緒池:

      public static ExecutorService newCachedThreadPool()

2.建立固定大小的執行緒池:

     public static ExecutorService newFixedThreadPool(int nThreads)

3.單執行緒池

     public static ExecutorService newSingleThreadExecutor()

4.建立排程執行緒池

     public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

執行緒池的三大優點

   a.降低資源消耗:通過重複利用已建立的執行緒降低執行緒建立與銷燬帶來的消耗

   b.提高響應速度:當任務到達時,不需要等待執行緒建立就可以立即執行

   c.提高執行緒建立的可管理性:使用執行緒池可以統一進行執行緒分配、排程和監控

執行緒池的組成

1. corePool:核心執行緒池

2. BlockingQueue: 阻塞佇列

3. MaxPool: 執行緒池容納的最大執行緒容量

a. 如果當前執行的執行緒數 < corePoolSize ,則建立新的執行緒執行任務,然後將其放入corePool(需要全域性鎖)

b. 如果當前執行緒數 >= corePoolSize,將任務放入阻塞佇列等待排程執行 (95%)

c. 如果阻塞佇列已滿,則試圖建立新的執行緒來執行任務(需要全域性鎖)

d. 如果建立執行緒後,匯流排程數 > maxPoolSize,則任務被拒絕,呼叫拒絕策略返回給使用者

工作執行緒

               執行緒池建立執行緒時,將執行緒封裝為worker,worker在執行完任務後,還會迴圈從工作佇列中取得任務來執行

手工建立執行緒池的六個引數

ThreadPoolExecutor tye = new ThreadPoolExecutor(

     int corePoolSize,

     int maximumPoolSize,

     long keepAliveTime,

     TimeUnit unit,

     BlockingQueue<Runnable> workQueue,

     RejectedExecutionHandler handler

)

1. corePoolSize(核心執行緒池): 當提交一個任務到執行緒池時,執行緒池會建立一個新的執行緒執行這個任務,即使其他基本執行緒也可以執行這個任務也會建立                                                       新的執行緒,直到當前執行緒池中的執行緒數量大於基本大小

2. BlockingQueue(阻塞佇列):  用於儲存等待執行任務的阻塞佇列

     a. ArrayBlockingQueue: 基於陣列的有界阻塞佇列。按照FIFO對元素排序

     b. LinkedBlockingQueue: 基於連結串列的無界阻塞佇列,吞吐量高於 ArrayBlockingQueue

                                              FixedThreadPool(),SingleThreadPool() 都用此佇列

     c. SynchronousQueue: 不儲存元素的阻塞佇列,每個插入操作必須等待另一個執行緒移除操作,否則插入操作一直處於阻塞狀態

                                           吞吐量 > LinkedBlockingQueue

                                           cachedThreadPool()採用此佇列

     d. PriorityBlockingQueue: 具有優先順序的無界阻塞佇列

3.keepAliveTime(執行緒活動保持時間): 執行緒的工作執行緒空閒後,保持存活的時間

4.TimeUnit : KeepAliveTime 的時間單位

5.RejectedExecutionHandler(飽和策略)(拒絕策略):執行緒池滿時無法處理新任務的執行策略

執行緒池預設採用 AbortPolicy (丟擲異常)-----可以省略此引數

6.死鎖的產生原因(四大條件)以及處理方案(銀行家演算法)

     a. 互斥條件:程序對所分配的資源進行排他性使用,即一段時間內,某資源只能由一個程序佔用,如果此時還有其他程序請求該資源,則請求者只能等                               待     

     b. 請求和保持條件:即程序已經保持至少保持一個資源,但是還在請求別的資源,但此時別的資源被其他執行緒持有,此時請求程序阻塞,此程序也不                                         想放棄所持有的資源

     c. 不剝奪條件:程序已經獲得的資源,不能被剝奪,只能由程序使用完自己釋放

     d. 環路等待條件:在發生死鎖時,必然存在一個競爭資源的環形鏈。即程序集合中 {p0,p1,p2,...,pn},p0等待p1的資源,pn等待p0的資源

銀行家演算法

                 是用來避免作業系統出現死鎖的有效演算法

7. volatile 兩層語義 - 懶漢式單例為何使用雙重加鎖

   a.禁止指令重排

   b.保證記憶體可見性

// 懶漢式單例模式
// 宣告 : 程式碼中出現的問題前提是 第二行程式碼未被 volatile修飾

public class Singleton{                             // 1
    private static volatile Singleton singleton;    // 2
    
    private Singleton(){}
    
    public static Singleton getInstance(){          // 3
        // 雙重檢查
        if(singleton==null){                        // 4 第一次檢查
            synchronized(Singleton.class){          // 5 加鎖
                if(singleton==null){                // 6 第二次檢查
                    return singleton = new Singleton();  // 7 問題在這裡
                }  
            }
            
        }
        return singleton;    

    }
        

}

 在多執行緒情況下,上面第七行程式碼 singleton = new Singleton(); 建立一個物件分為三步

memory = allocate(); // 1:分配物件的記憶體空間

ctorInstance(memory); // 2:初始化物件

singleton = memory; // 3.設定 singleton 指向 剛分配的記憶體地址


上面三行程式碼可能會被重排序:

memory = allocate(); // 1:分配物件的記憶體空間

singleton = memory; // 3.設定 singleton 指向 剛分配的記憶體地址

ctorInstance(memory); // 2:初始化物件

這裡由一個問題就是,在還沒有初始化物件的時候就將singleton指向了記憶體地址

8.NIO(Netty)

NIO如何實現多路複用,BIO,NIO,AIO特點

  多路複用:在Java 1.4中引入了NIO框架(Java.nio包),提供了Channel,Selector,Buffer等新的抽象類,可以構建多路複用的,同步非阻塞IO程式,同時提供了更接近作業系統底層的高效能資料操作方式

  AIO:在JDK1.7中,NIO有了新一步改進,既 NIO2 ,引入了非同步非阻塞IO方式,也叫AIO,

                   非同步IO操作基於事件和回撥機制,可以簡單理解為,應用操作直接返回,不會阻塞在那裡,當後臺處理完成,作業系統會通知相應執行緒進行後                     續工作 

 BIO,NIO,AIO 特點:

   BIO:該方式適用於數目比較小且固定的架構,這種方式對於伺服器資源要求比較高,併發侷限於應用中,是JDK1.4以前唯一的選擇,但程式直觀簡單易理解

   NIO: 該方式適用於數目比較多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4以後支援

   AIO: 適用於數目比較多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參加併發操作,程式設計比較複雜,JDK1.7開始支援