Java學習總結 1-1-3 J.U.C併發編程包詳解

筆記記錄,整理的亂七八糟~~

 

--> Lock鎖
    獲取鎖的幾種方式:
        void lock();  不死不休
        boolean tryLock();  淺嘗輒止
        boolean tryLock(long time, TimeUnit unit)throws InterruptedException; 過時不候
        void localInterruptibly() throws InterruptedException; 任人擺佈
    釋放鎖:
        void unlock;
        
        lock最常用,localInterruptibly一般比較昂貴,有的impl可能還沒有實現locakInterruptibly(),只有真的需要效應中斷時才使用

    Condition:new ReentrantLock().newCondition()
        
        Condition必須在加鎖之後使用await或signal方法,Condition可以有多個
        
        wait/await:線程等待時同時擁有釋放鎖的語義
        
        
--> ReentrantLock:互斥鎖
        ReentrantLock有兩個重要的屬性實現,owner、count和waiterrs,表示當前哪個線程加的鎖、加鎖的次數和搶鎖失敗等待
        的隊列。加鎖的次數需要和解鎖的次數相同
            owner:保存當前持有鎖線程的引用
            count:加鎖次數,解鎖次數需與加鎖次數相同,否則鎖不處於可以被爭搶的狀態
            waiterrs:搶鎖失敗進入隊列,進入該隊列的線程將進行等待(Watting狀態),等待持有鎖的線程執行完自動喚醒
            
        Synchronized:
            優點:1、使用簡單,語義清晰,
                  2、由JVM提供,提供了多種優化方案(鎖粗化、鎖消除、偏向鎖、輕量級鎖)
                  3、鎖的釋放由虛擬機來完成,不用人工干預,也降低了死鎖的可能性
            缺點:無法實現一些鎖的高級特性:公平鎖、中斷鎖、超時鎖、讀寫鎖、共享鎖
        Lock:
            優點:可實現鎖的高級特性:公平鎖、中斷鎖、超時鎖、讀寫鎖、共享鎖等等
            缺點:需手動釋放鎖unlock,使用不當可能會造成死鎖
        
        
--> ReaadWriteLock :讀寫鎖(適用於讀取操作多於寫入的場景,改進互斥鎖的性能。例:集合的併發線程安全新改造、緩存組件)
        維護一對關聯,一個只用於讀操作,一個只用於寫操作;讀操作可以由多個讀線程同時持有,寫鎖是排他的。同一時間,兩
        把鎖不能被不同線程持有 當線程爭搶讀鎖時,首先判斷寫鎖值是否爲零,爲零則獲取讀鎖。爭搶寫鎖時首先判斷讀鎖值是
        否爲零,爲零則判斷寫鎖是否爲零,爲零則持有該鎖,修改writeCount和owner,爭搶失敗的線程進入waiterrs隊列進入等
        待狀態,等持有鎖的線程執行完後喚醒隊列頭部的線程
                readCount:讀鎖的線程數,可被多個線程持有,數字隨線程數累加
                writeCount:寫鎖重入的次數 ,同一時刻只能被一個線程持有但可被重入多次,解鎖次數需與加鎖次數相同
                owner:保存當前持有鎖線程的引用,只用於寫鎖,讀線程佔有鎖時該值爲null
                waiterrs:搶鎖失敗進入隊列,進入該隊列的線程將進行等待(Watting狀態),等待持有鎖的線程執行完自動喚醒
        
        鎖降級:
            指寫鎖降級成爲讀鎖。持有寫鎖的同時,在獲取讀鎖,隨後釋放寫鎖的過程。寫鎖是線程獨佔,讀鎖是共享,所以
            寫-->讀是降級。(讀->寫是不能實現的)


--> 併發類容器Map
    HashMap、ConcurrentHashMap

    HashMap:線程不安全
        怎麼存儲的,結構是什麼?
            1.7:Entry<K,V>[] + 鏈表
                 擴容:resize(int newCapacity)方法,將數組版裏的元素重新哈希到新的數組裏,在原數組達元素個數到長度的0.75倍
                           時(不是數組的長度),擴容大小到原來的兩倍
            1.8:Entry<K,V>[] + 鏈表(當鏈表元素>=8時,轉爲紅黑樹,優化查詢性能)

            擴容時只是修改引用,並非複製數據

        怎麼查找數據,算法?
            1.7:先把Entry的Key值取Hash值,然後取模,計算的結果作爲數組的下標判斷該下表對應的位置是否有元素,如果沒
                     有新建節點存儲,如果有則遍歷鏈表所有元素,便利時如果遇到相同key則替換,否則在便利完後添加新的節點
            1.8:過程和1.7大同小異,只是在鏈表元素>=8時轉爲紅黑樹,便利的是紅黑樹而不是普通的鏈表

        兩種Map初始化大小時指定map長度最好爲16的整倍數,不指定默認爲16

    ConcurrentHashMap:線程安全的HashMap
        怎麼存儲的,結構是什麼?
            1.7:segments[] + Hashtablle,數組+鏈表。數組的每個元素都是特殊的hashtable,採用分段鎖的機制提供併發訪問,
                     數組長度一經初始化不會變。擴容不是針對、segmennts數組來講。根據Hash值獲取需要存放在數組哪個角標(即
                     哪個Hashtable),來存儲數據   
            1.8:數組 + 鏈表: 添加元素時,計算hash值取模得到數組的下標,如果該下標的元素爲空,通過CAS操作在該下標位
                     置新建node,吧元素存入鏈表的頭部,若失敗則重新檢查該下標是否爲空,不爲空則添加synchronzed對整個鏈
                     表添加鎖來添加元素,鏈表元素到8之後轉換爲紅黑樹

        相比1.7,1.8的ConcurrentHashMap優勢?
            1.7採用的Hashtable數據結構比較重,數組長度一經初始化不能被改變,對併發量增大不友好,hashtable內元素無限增
                 大,導致效率變低。
            1.8採用數據較簡單的數據結構,可擴容,並且只對數組所在下標的鏈表做鎖,鎖住的數據力度較小,併發支持較好

        ConcurrentSkipListMap:有順序的key,線程安全的TreeMap
            跳錶結構:


--> List Set Queue
    List:數組結構、有序、元素可重複
        ArrayList:
            存儲結構:數組(Object[] elementData)、非線程安全
            初始化長度:不指定默認爲0,不指定長度下第一次調用add方法重新計算數組長度並賦值爲10
            擴容機制:
                添加時數組元素個數(size)+1後大於數組長度時擴容
                    1、計算原數組長度1.5倍的長度作爲擴容後的數組長度
                    2、調用JDK的數組複製方法(Arrays.copyOf(Object[],int capacity);)將原數組內容複製到新創建的數組並重新賦值給
                          elementData
        
        CopyOnWirteArrayList:
            在高併發下要求ArrayList具有線程安全的時候,無非採用synchronized和互斥鎖(ReadWriteLock)兩種方式,
            synchronized效率太低不考慮,ReadWriteLock在讀多寫少時可能會出現在執行一個寫操作時,加上寫鎖之後同一時間大
            量讀操作被阻塞無法執行,所以引入CopyOnWrite思想。
            
            CopyOnWirte思想:
                CopyOnWrite可被理解爲“使用拷貝的副本執行寫操作”,原數組用Volatile修飾,在需要修改數組裏元素時,先拷貝這個
                數組做爲副本,寫操作只對副本進
                行(CAS操作),一旦寫操作執行完成之後,將副本賦值給原數組引用,達到對讀線程即時可見

                擴容機制:添加時進行擴容,每次長度只擴容1

                缺點:將數據全量複製,佔用空間且浪費性能

                優點:讀寫分離,讀時不需要鎖


    Set:K,V結構、無序、元素不可重複
        HashSet:基於HashMap實現,非線程安全
        CopyOnWriteArraySet:基於CopyOnWirteArrayList實現,線程安全
                            添加時校驗元素是否已存在,只添加未存在的元素,所以元素不可重複
        ConcurrentSkipListSet:基於ConcurrentSkipListMap實現,線程安全,有序,查詢快
    

    Queue(隊列、管道結構):先進先出
    棧(半封閉結構):後進先出

        Queue API:
            非阻塞 API:對隊列操作時不等待,如果得不到期望的結果則返回null
                offer:添加一個元素並返回true,對列已滿則返回false
                poll :移除並返回對列頭部的元素,對列爲空則返回null
                peek :返回對列頭部的元素,對列爲空則返回null

            阻塞 API :對隊列操作時得不到期望的結果則一直等待
                put :添加一個元素,隊列滿則等待
                take:移除並返回列頭部的元素

            不常用(非阻塞):
                add:增加一個元素,隊列滿則拋異常
                remove:移除並返回列頭部的元素,隊列爲空則拋異常
                element:返回對列頭部的元素,隊列爲空則拋異常

        ArrayBlockIngQueue:阻塞、線程安全
            結構:數組 + putIndex + takeIndex + couunt,以數組形式實現對列,兩個下標實現先進先出,count爲當前數組內的元素
                      個數。內部維護一個數組,putIndex和takeIndex默認爲0,通過構造函數指定數組容量大小

                put():添加元素時
                    當添加元素(調用put()方法)時,若count和元素的長度相等(數組已滿)則移入等待對列(notFull.await()),否則
                    通過讀取putIndex獲取數組下標使元素添加至相應位置並修改putIndex值爲下個元素應存放的位置(putIndex+1)修
                    改後大於數組本身下標時則重置爲0並記錄元素個數(count++),並喚醒notEmpty線程;

                take():刪除元素時
                    刪除元素(調用take()方法)時,若如果數組爲空則移入等待對列(notEmpty.await());反之通過讀取takeIndex獲
                    取數組下標刪除元素並修改takeIndex值爲下個元素應存放的位置(takeIndex+1),修改後大於數組本身下標時則重
                    置爲0並記錄元素個數(count—-),同時喚醒notFull線程

        LinkedBlockingQueue:鏈表、阻塞、線程安全
            結構:鏈表、線程安全
                維護一個鏈表,初始化不指定大小則默認爲Intrger.MAX_VALUE,與ArrayBlockingQueue主要的區別是,
                LinkedBlockingQueue在插入數據和刪除數據時分 別是由兩個不同的lock(takeLock和putLock)來控制線程安全的,
                也由這兩個lock生成了兩個對應的condition(notEmpty和notFull)來實現可阻塞的插入和刪除數據

            put():添加元素時
                當添加元素時,添加至鏈表的頭部,如果隊列已滿,則阻塞當前線程,將其移入等待隊列。若隊列滿足插入數據的條
                件,則通知被阻塞的生產者線程(notFull.signal();)

            take():刪除元素時
                當刪除元素時:若當前隊列爲空,則阻塞當前線程,將其移入到等待隊列中,直至滿足條件。否則移除對列頭元素;
                如果當前滿足移除元素的條件,則通知被阻塞的消費者線程
        
        ArrayBlockIngQueue和LinkedBlockingQueue比較:
            相同點:ArrayBlockingQueue和LinkedBlockingQueue都是通過condition通知機制來實現可阻塞式插入和刪除元素,並滿
                          足線程安全的特性;
            不同點:1、ArrayBlockingQueue底層是採用的數組進行實現,而LinkedBlockingQueue則是採用鏈表數據結構;

                          2、ArrayBlockingQueue插入和刪除數據,只採用了一個lock,而LinkedBlockingQueue則是在插入和刪除分別
                               採用了putLock和takeLock,這樣可以降低線程由於線程無法獲取到lock而進入WAITING狀態的可能性,從而
                               提高了線程併發執行的效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章