1. 線程安全性
- 構建并發(fā)程序時, 必須正確的使用線程和鎖.
- "共享"意味著變量可以由多個線程同時訪問, 而"可變"則意味著變量的值在其生命周期內可以發(fā)生變化.
- 正確的編程方法: 先使代碼正確運行, 然后再提高代碼的速度.
- 線程安全:當多個線程訪問某個類時, 這個類始終表現(xiàn)出正確的行為, 那么就稱這個類為線程安全類.
- 競態(tài)條件: 在并發(fā)編程中, 由于不恰當?shù)膱?zhí)行時序而出現(xiàn)不正確的結果的情況.[常見類型: 先檢查后執(zhí)行]
- 延遲初始化的目的是將對象的初始化操作推遲到實際被使用時才進行, 同時確保值被初始化一次.
- 內置鎖: java通過內置鎖機制來支持原子性: 同步代碼塊(Synchronized Block) . 每個java對象都可以用作一個實現(xiàn)同步的鎖, 這些鎖稱為內置鎖(Intrinsic Lock)或監(jiān)視器鎖(Monitor Lock). 線程進入同步代碼塊之前會自動獲取鎖, 退出時釋放鎖. Java內置鎖相當于一種互斥鎖, 這意味著最多只有一個線程能只有這中鎖.
- 重入: 當某個線程請求一個由其他線程持有的鎖時, 發(fā)出的請求會阻塞. 而內置鎖是可重入的, 如果某個線程視圖獲得一個已經(jīng)由它自己(同一個線程 )持有的鎖, 那么這個請求會成功. 當線程請求一個未被持有的鎖時, JVM將記下鎖的持有者, 并且將獲取計數(shù)器置為1, 如果同一個線程再次獲取這個鎖, 計數(shù)器將遞增, 退出時,計數(shù)器遞減. 當計數(shù)器為0時, 這個鎖被釋放.
- 串行訪問意味著多個線程依次以獨占的方式訪問對象, 而不是并發(fā)訪問. 串行機制通常都無法提供高吞吐率或快速響應性.
2. 對象的共享
- 重排序: 在沒有同步的情況下, 編譯器, 處理器,以及運行時等都可以對操作的順序進行一些意想不到的調整, 目的是提高性能.
- Java內存模型要求, 變量的讀取和寫入操作都必須是原子操作, 但對于非volatile類型的long和double變量, JVM允許將64位的讀操作或寫操作分解為兩個32位的操作.
- Volatile變量: Java語言提供了一種稍弱的同步機制, 即Volatile變量, 用來確保將變量的更新操作通知到其他線程. volatile變量不會進行重排序, 不會被緩存到寄存器或者對其他處理器不可見的地方, 因此在讀取volatile類型的變量時總會返回最新的寫入值. 由于在訪問volatile變量時不會執(zhí)行加鎖操作, 所以不會使執(zhí)行的線程阻塞. volatile是比synchronized關鍵字更輕量級的機制. 從內存可見性的角度來看, 寫入volatile變量相當于退出同步代碼塊, 讀取相當于進入同步代碼塊.
- 使用volatile場景:
1. 當變量的寫入操作不依賴變量的當前值, 或者能確保只有單個線程更新變量的值.
2. 該變量不會與其他狀態(tài)變量一起納入不變性條件中.
3. 在訪問變量時不需要加鎖. - ThreadLocal類: 維持線程封閉性更規(guī)范的方法是使用ThreadLocl, 這個類能使線程中的某個值與保存值關聯(lián)起來. ThreadLocal提供了get和set等方法,這些方法為每個使用該變量的線程都保存一份獨立的副本, 因此get總是返回由當前執(zhí)行線程在調用set時設置的最新值. ThreadLocal對象通常用于防止對可變的單實例變量(Singleton)或全局變量進行共享. 當某個線程初次調用ThreadLocal.get方法時, 就會調用initialValue來獲取值. 這些特定于線程的值保存在Thread對象中, 當線程終止后, 這些值作為垃圾回收.
- Final域: final類型的域是不能改的(但如果final域所引用的對象是可變的, 那么這些被引用的對象可以是可修改的). 在java內存模型中, final域能確保初始化過程的安全性, 從而可以不受限制的訪問不可變對象, 并在共享這些對象時無須公布.
3. 基礎構建模塊
- 同步容器類都是線程安全的, 它們通過其自身的鎖來保護它的每個方法, 將所有對容器狀態(tài)的訪問都串行化, 降低了并發(fā)性.
- 持有鎖的時間越長, 那么在鎖上的競爭就可能越激烈, 如果許多線程都在等鎖釋放, 那么將極大地降低吞吐量和CPU的利用率.
- 編譯器將字符串的連接操作轉換為StringBuilder的apped(Object), 這個方法又會調用容器的toString方法, 標準容器的toString方法將迭代容器, 并在每個元素上調用toString來生成容器內容的格式化標識.
- Queue上的操作不會阻塞, 如果隊列為空, 獲取元素返回空值. BlockingQueue擴展了Queue, 增加了可阻塞的插入和獲取等操作[隊列滿時阻塞插入, 空時阻塞獲取].
- ConcurrentHashMap: 與HashMap一樣, 是一個基于散列的Map, 但它使用完全不同的加鎖策略(分段鎖, Lock Striping. 16個段)來提供更高的并發(fā)性和伸縮性. 任意數(shù)量的讀取線程可以并發(fā)地訪問Map, 執(zhí)行讀操作的線程和執(zhí)行寫操作的線程可以并發(fā)地訪問Map, 并且一定數(shù)據(jù)的寫入線程可以并發(fā)地修改Map. 只有當應用程序需要加鎖Map進行獨占訪問時, 才應該放棄使用ConCurrentHashMap.
- CopyOnWriteArrayList: 是一個線程安全的鏈表, 特別適用于管理監(jiān)聽器列表. 有良好的并發(fā)性, 并且在迭代期間不需要對容器進行加鎖或復制. 每次修改時, 都會創(chuàng)建并重新發(fā)布一個新的容器副本, 從而實現(xiàn)可變性. 不會拋出ConcurrentModificationException. 僅當?shù)僮鬟h遠多余修改操作時, 才應使用"寫入時復制"容器.
- DeQue: 是一個雙端隊列, 實現(xiàn)了在隊列頭和隊列尾的高效插入和移除. 具體實現(xiàn)有ArrayQueue和LinkBlockingDeque.
- 中斷是一種協(xié)作機制. 最常使用中斷的情況是取消某個操作. 方法對中斷的請求響應速度越高, 就越容易及時取消那些執(zhí)行時間很長的操作.
- 閉鎖: 是一種同步工具類, 可以言辭線程的進度直到達到終止狀態(tài). CountDownLatch是一種靈活的閉鎖實現(xiàn), 它可以使一個或多個線程等待一組事件的發(fā)生. 計數(shù)器為0表示所有要等待的時間都已經(jīng)發(fā)生, 非0會一直阻塞. FutureTask也可以做閉鎖.
- 信號量: 計數(shù)信號量(Counting Semaphore)用來控制同時訪問某個特定資源的操作數(shù)量, 或同時執(zhí)行某個指定操作的數(shù)量. 計數(shù)信號量還可以用來實現(xiàn)某種資源池, 或者對容器加邊界. Semaphore中管理者一組虛擬的許可(permit), 許可的初始數(shù)量可通過構造函數(shù)來指定. 計數(shù)信號量是一種簡化形式的二值信號量, 即初始值為1的Semaphore. 二值信號量可以用做互斥體(mutex), 并具備不可重入的加鎖語意: 誰擁有這個唯一的許可,誰就擁有了互斥鎖.
- 柵欄(barrier): 類似于閉鎖,它能阻塞一組線程知道某個事件發(fā)生. 區(qū)別在于: 所有線程必須同時達到柵欄位置, 才能繼續(xù)執(zhí)行. 閉鎖用于等待事件, 而柵欄用于等待其他線程. CyclicBarrier: 可以使一定數(shù)量的參與方式反復地在柵欄位置匯集, 它在并行迭代算法中非常有用.
4.任務執(zhí)行
- 任務: 通常是一些抽象的且離散的工作單元. 在理想情況下, 各個任務之間是相互獨立的: 任務并不依賴于其他任務的狀態(tài)最疆、結果或者邊界效應.
- Executor框架: 能支持多種不同的任務執(zhí)行策略, 還提供了生命周期的支持, 以及統(tǒng)計信息收集, 應用程序管理機制和性能監(jiān)控等機制.
- 線程池: 是指管理一組同構工作線程的資源池. 工作者線程(Worker Thread)的任務很簡單: 從工作隊列中獲取一個任務, 執(zhí)行任務, 然后返回線程池等待下一個任務. 通過重用現(xiàn)有的線程而不是創(chuàng)建新線程, 可以在處理多個請求時分攤線程創(chuàng)建和銷毀過程中產(chǎn)生巨大的開銷.
- Executors中的靜態(tài)方法創(chuàng)建線程池:
1.newFixedThreadPool: 創(chuàng)建一個固定長度的線程池, 每當提交一個任務時就創(chuàng)建一個線程, 知道達到線程池的最大數(shù)量, 這是線程池規(guī)模不再變化(如果某個線程由于發(fā)生了未逾期的Exception異常而結束, 那么線程池會補充一個新的線程).
2. newCachedThreadPool: 創(chuàng)建一個可緩存的線程池, 如果線程池的當前規(guī)模超過了處理需求時, 將回收空閑線程, 需求增加時, 添加新的線程, 線程池的規(guī)模不存在任何限制.
3. newSingleThreadPool: 是一個單線程的Executor, 它創(chuàng)建單個工作者線程來執(zhí)行任務, 如果線程異常結束, 會創(chuàng)建另外一個線程來替代. 能確保依照隊列中的順序來串行執(zhí)行.
4. newScheduledThreadPool: 創(chuàng)建一個固定長度的線程池, 而且可以延遲或定時的方式來執(zhí)行任務. - JVM只有在所有(非守護)線程全部終止后才會退出.
- ExecutorService的生命周期有3中狀態(tài): 運行捆探、關閉和已終止.showdown方法將執(zhí)行平緩的關閉過程: 不再接受新任務, 同時等待已提交的任務執(zhí)行完成-包括那些未開始執(zhí)行的任務. shutdownNow方法將執(zhí)行粗暴的關閉過程: 它將嘗試取消所有運行中的任務, 并且不再啟動隊列匯總未開始執(zhí)行的任務.
- 如果要構建自己的調度任務, 可以使用DelayQueue, 它實現(xiàn)了BlockingQueue, 并且為ScheduledThreadPoolExecutor提供調度功能.
- Executor執(zhí)行的任務有4個生命周期階段: 創(chuàng)建、提交、開始和完成. 在Executor框架中, 已提交但尚未開始的任務可以取消, 但對于那些已經(jīng)開始執(zhí)行的任務, 只有當它們響應中斷時, 才能取消.
- Future: 標識一個任務的生命周期, 并提供了響應的方法來判斷是否已完成或取消, 以及獲取任務的結果和取消任務等. 在Future規(guī)范中包含的意義是, 任務的生命周期只能前進, 不能后退, 就像ExecutorService的生命周期一樣. 當任務完成后, 他就永遠留在"完成"狀態(tài)上.
- CompletionService(完成服務): 將Executor和BlockingQueue的功能融合在一起. 可以將Callable任務提交給它來執(zhí)行, 人后使用類似于隊列操作的take和poll等方法來獲得已完成的結果, 而這些結果會在完成時被封裝為Future. ExecutorCompletionService實現(xiàn)了CompletionService, 并將計算部分委托給一個Executor.
失效數(shù)據(jù).緩存污染(Cache Pullution). 緩存逾期. 線程泄漏(thread Leakage)
入列[Enqueue]. 出列[Dequeue].
LinkedBlockingQueue和是FIFO隊列, PriorityBlockingQueue是按優(yōu)先級隊列.
AtomicReference是一種替代對象引用的線程安全類.
委托是創(chuàng)建線程安全類的一個最有效的策略: 只需要讓現(xiàn)有的線程安全類管理所有的狀態(tài)即可.