線程基礎
線程與進程的區別
- 進程是程序的一次執行過程。它資源分配的單位。
- 線程是程序執行的單位。
并行和并發的區別
- 單核CPU下,線程串行。(并發:多線程輪流使用一個或多個CPU)
- 多核CPU下,每個核都可調度線程。(并行:多CPU同時執行多個線程。
創建線程的方式
- 繼承Thread類
- 實現Runnable接口
- 實現Callable接口
- 線程池創建線程
runnable和callable的區別
- Runnable接口run方法沒有返回值;Callable接口call方法有返回值
- Runnable接口run方法的異常只能內部消化不能上拋;Callable接口call方法允許拋出異常
線程的run()和start()的區別
- start():用于啟動線程,只能被調用一次。
- run():封裝要被線程執行的代碼,可以被調用多次。
wait()和sleep()方法的區別
1.方法歸屬不同
- sleep() 是Thread類的靜態方法
- wait() 是Object類的成員方法,任何對象都有
2.喚醒時機不同
- 執行 sleep() 的線程會在等待相應毫秒后喚醒
- wait() 可以被?notify() 喚醒
3.鎖特性不同
- wait()方法的調用必須先獲取對象的鎖;sleep()不需要
- wait()執行完會釋放對象鎖;sleep()若在synchronized中執行,不釋放對象鎖
線程的狀態與轉換條件
三個線程如何順序執行
使用線程中的join()方法解決。
t.join():等待線程運行結束。阻塞調用此方法的線程,直到t線程執行完成。
并發安全
Synchronized
實現原理
基于進入和退出Monitor對象來實現方法同步和代碼塊同步。
- 方法級的同步是隱式,JVM可以通過?ACC_SYNCHRONIZED 訪問標志區分一個方法是否同步方法。
- 代碼塊的同步是利用monitorenter和monitorexit這兩個字節碼指令。
底層實現
synchronized的底層實現是依賴于Java對象頭,以及Monitor對象監視器。
Monitor監視器鎖有三個重要屬性:_Owner 、_WaitSet 和 _EntryList 。
- _owner指向持有ObjectMonitor對象的線程。
- 當多個線程同時訪問時,首先會進入 _EntryList 集合等待。
- 當線程獲取到對象的monitor 后,把monitor中的owner變量設置為當前線程,同時monitor中的計數器count加1。
- 若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復為null,count自減1,同時該線程進入 _WaitSet集合中等待被喚醒。
Monitor實現的鎖屬于重量級鎖,于是有了鎖升級,基于對象頭(MarkWord)。
- 最輕程度的鎖為偏向鎖,資源總是由同一線程多次獲得。偏向鎖只依賴于鎖對象,鎖對象在64位虛擬機里由64Bit的Markword來控制,線程獲取鎖時,通過CAS方式將線程ID設置到對象頭里的MarkWord的Thread ID中,線程ID指針在MarkWord中占用54個比特位。偏向鎖的標識位為101
- 當對象進行了hash操作,那么鎖就會失效,因為HashCode在MarkWord中占用31個比特位。無鎖的表示位為001
- 接著被另外的線程所訪問,偏向鎖升級為輕量級鎖,MarkWord中設置指向線程棧的lock record指針。其他線程會自旋嘗試獲取鎖,不會阻塞。輕量級鎖的標識位為000
- 一旦線程競爭,升級為重量級鎖,其他線程都會被阻塞。重量級鎖的標識位為010
JMM(java內存模型)
- JMM把內存分為兩塊,一塊是私有線程的工作內存,一塊是所有線程的共享區域(主內存)。
- 線程與線程間相互隔離,交互需通過主內存。
Volatile關鍵字
- 保證線程間的可見性:修飾共享變量,防止編譯器等優化發生,讓線程對共享變量的修改對另一個線程可見。
- volatile禁止指令重排序:修飾共享變量會在讀、寫共享變量時加入不同屏障,阻止其他讀寫操作,從而阻止重排序。
AQS
全稱為AbstractQueuedSynchronizer(抽象隊列同步器)。它是構建鎖的基礎框架。
- AQS中有個屬性state表示狀態,0為無鎖,1為有鎖。
- 還維護了一個雙向隊列作為FIFO隊列,其他線程會進入隊列等待,線程釋放鎖時會喚醒隊列中head的元素。
ReentrantLock(可重入鎖)
可重入鎖指:調用lock()方法獲取了鎖后,再調用lock(),是不會再阻塞的。
ReentrantLock利用CAS+AQS隊列實現。支持公平鎖和非公平鎖。
Synchronized和Lock的區別
- synchronized是關鍵字,在 jvm 中由c++實現;Lock 是接口,在 jdk 中由 java 實現。
- synchronized退出鎖時自動釋放;Lock需要調用unlock方法釋放。
- Lock的功能比synchronized多,如公平鎖。
死鎖產生的條件
互斥,請求保持,不可剝奪、循環等待。
(一個線程需要同時獲取多把鎖,容易發生死鎖。)
線程池
線程池核心參數
- 核心線程數
- 最大線程數
- 生存時間(救急線程的生存時間)
- 時間單位
- 任務隊列:當沒有空閑核心線程時,新任務到此隊列等待,隊列滿就會創建救急線程。(ArrayBlockingQueue 和 LinkedBlockingQueue)
- 線程工廠
- 拒絕策略:當所有線程在忙,工作隊列也滿,才會觸發。
- AbortPolicy:拋出異常,默認策略;
- CallerRunsPolicy:調用者的線程來執行;
- DiscardOldestPolicy:丟棄阻塞隊列中最靠前的任務,執行當前任務;
- DiscardPolicy:丟棄任務。
如何確定核心線程數
- IO密集型任務:核心線程數大小設置為2N+1
- CPU密集型任務(或者高并發、任務執行時間短):核心線程數大小為N+1
線程池的種類
- Executors.newFixedThreadPool():固定大小的線程池,核心線程數與最大線程數相等
- Executors.newSingleThreadExecutor():單線程化的線程池,保證任務FIFO執行
- Executors.newCachedThreadPool():可緩存的線程池,核心線程數為0
- Executors.newScheduledThreadPool():提供了“延遲”和“周期執行”功能
不推薦用Executors創建線程池
應該使用7個參數的ThreadPoolExecutor的方式,按需設置核心線程數和最大線程數,避免無限隊列長度,規避OOM。
ThreadLocal
ThreadLocal是解決線程安全問題的一個操作類,它為每個線程分配一個獨立的內部存儲空間,實現了線程內的資源共享。
- set(value):設置值。根據當前線程對象,通過getMap()獲取ThreadLocalMap。
- get():獲取值。通過getEntry()獲取ThreadLocalMap中的Entry對象。通過HashCode & (數組長度 - 1) 定位數組下標。
- remove():清除值。同get()
ThreadLocal內存泄漏
強引用:表示一個對象處于有用且必須的狀態,GC無法回收處于強引用的對象,即便出現OOM。
User user = new User();
弱引用:表示一個對象處于可能有用但非必須的狀態。GC一旦發現弱引用,會回收相關聯的對象。
User user = new User();
WeakReference weakRef = new WeakReference(user);
每一個Thread維護一個ThreadLocalMap,Entry對象繼承了WeakReference。其中key是弱引用的ThreadLocal實例,value是強引用的線程變量副本。
避免內存泄漏
在使用ThreadLocal后主動使用remove()方法釋放key、value。