面試題整理:多線程
程序是資源配置設定的基本機關,線程是CPU排程和執行的基本機關。
線程依賴于程序,一個線程隻屬于一個程序,一個程序可以有多個線程。
程序配置設定有獨立的記憶體資源,線程共享程序的記憶體資源。
程序的建立、切換開小大,線程的建立和切換開銷小,是以也稱為輕量級程序。
并發:同一時間段,多個任務都在執行 (同一時刻隻有一個任務執行);
并行:同一時刻,多個任務同時執行。
破壞互斥條件 : 這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥通路)。
破壞請求與保持條件 : 一次性申請所有的資源。
破壞不剝奪條件 : 占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源。
破壞循環等待條件 : 靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環等待條件。
new 一個 Thread,線程進入了建立狀态。調用 start() 方法,會啟動一個線程并使線程進入了就緒狀态,當配置設定到時間片後就可以開始運作了。 start() 會執行線程的相應準備工作,然後自動執行 run() 方法的内容,這是真正的多線程工作。 但是,直接執行 run() 方法,會把 run()方法當成一個 main 線程下的普通方法去執行,并不會在某個線程中執行它,是以這并不是多線程工作。
總結: 調用 start() 方法方可啟動線程并使線程進入就緒狀态,直接執行 run() 方法的話不會以多線程的方式執行。
1、synchronized 關鍵字解決的是多個線程之間通路資源的同步性, synchronized 關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻隻能有一個線程執行。
2、synchronized 關鍵字最主要的三種使用方式:
修飾執行個體方法: 作用于目前對象執行個體加鎖,進入同步代碼前要獲得目前對象執行個體的鎖。
修飾靜态方法: 也就是給目前類加鎖,會作用于類的所有對象執行個體 ,進入同步代碼前要獲得目前 class 的鎖。
修飾代碼塊: 指定加鎖對象,對給定對象/類加鎖。 synchronized(this|object) 表示進入同步代碼庫前要獲得給定對象的鎖。 synchronized( .class) 表示進入同步代碼前要獲得目前 class 的鎖。
構造方法不能使用 synchronized 關鍵字修飾。構造方法本身就屬于線程安全的,不存在同步的構造方法一說。
synchronized 同步語句塊的實作使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代碼塊的開始位置, monitorexit 指令則指明同步代碼塊的結束位置。
synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的确實是ACC_SYNCHRONIZED 辨別,該辨別指明了該方法是一個同步方法。
不過兩者的本質都是對對象螢幕 monitor 的擷取。
volatile 關鍵字是線程同步的輕量級實作,是以 volatile 性能肯定比 synchronized 關鍵字要好。但是 volatile 關鍵字隻能用于變量而 synchronized 關鍵字可以修飾方法以及代碼塊。
volatile 關鍵字能保證資料的可⻅性,但不能保證資料的原子性。 synchronized 關鍵字兩者都能保證。
volatile 關鍵字主要用于解決變量在多個線程之間的可⻅性,而 synchronized 關鍵字解決的是多個線程之間通路資源的同步性。
ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,而 value 是強引用。是以,如果ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候,key 會被清理掉,而 value 不會被清理掉。這樣一來, ThreadLocalMap 中就會出現 key 為 null 的 Entry。假如我們不做任何措施的話,value 永遠無法被 GC 回收,這個時候就可能會産生記憶體洩露。ThreadLocalMap 實作
中已經考慮了這種情況,在調用 set() 、 get() 、 remove() 方法的時候,會清理掉 key 為 null的記錄。
強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收。即使目前記憶體空間不足,JVM也不會回收它,而是抛出 OutOfMemoryError 錯誤,使程式異常終止。
軟引用:如果一個對象具有軟引用,它就不會被垃圾回收器回收。隻有在記憶體空間不足時,軟引用才會被垃圾回收器回收。這種引用常常被用來實作緩存技術。因為緩存區裡面的東西,之後在記憶體不足的時候才會被清空。
弱引用:如果一個對象具有弱引用,在垃圾回收時候,一旦發現弱引用對象,無論目前記憶體空間是否充足,都會将弱引用回收。不過由于垃圾回收器是一個優先級較低的線程,是以并不一定能迅速發現弱引用對象。
虛引用:如果一個對象具有弱引用,就相當于沒有引用,在任何時候都有可能被回收。虛引用是使用PhantomReference建立的引用,虛引用也稱為幽靈引用或者幻影引用,是所有引用類型中最弱的一個。一個對象是否有虛引用的存在,完全不會對其生命周期構成影響,也無法通過虛引用獲得一個對象執行個體。
降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗。
提高響應速度。當任務到達時,任務可以不需要的等到線程建立就能立即執行。
提高線程的可管理性。線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的配置設定,調優和監控。
newCachedThreadPool : 該方法傳回一個可根據實際情況調整線程數量的線程池。線程池的線程數量不确定,但若有空閑線程可以複用,則會優先使用可複用的線程。若所有線程均在工作,又有新的任務送出,則會建立新的線程處理任務。所有線程在目前任務執行完畢後,将傳回線程池進行複用。
newFixedThreadPool : 該方法傳回一個固定線程數量的線程池。該線程池中的線程數量始終不變。當有一個新的任務送出時,線程池中若有空閑線程,則立即執行。若沒有,則新的任務會被暫存在一個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務。
newSingleThreadExecutor : 方法傳回一個隻有一個線程的線程池。若多餘一個任務被送出到該線程池,任務會被儲存在一個任務隊列中,待線程空閑,按先入先出的順序執行隊列中的任務。
newScheduledThreadPool:建立一個定長線程池,支援定時及周期性任務執行。
核心線程數
2.總線程數
3.存活時間
4.存活時間機關
5.線程存儲隊列
5.1.有限隊列-SynchronousQueue(在newCachedThreadPool()方法中使用)
這是一個内部沒有任何容量的阻塞隊列,任何一次插入操作的元素都要等待相對的删除/讀取操作,否則進行插入操作的線程就要一直等待,反之亦然
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
有限隊列-ArrayBlockingQueue
是一個由數組支援的有界阻塞隊列(先進先出),假設初始隊列長度為2,當添加第三個元素時線程會進行阻塞。同樣的,當元素為空的時候,進行擷取也會進行阻塞。
5.2 無限隊列-LinkedBlockingQueue
建立ThreadPoolExecutor常用的隊列。
當不設定隊列初始長度,則為無界隊列。
當設定隊列初始長度則類似于ArrayBlockingQueue即插入與讀取都會為空進行堵塞。
6.ThreadFactory給一組線程用來命名,阿裡巴巴規範建議使用,友善以後的錯誤查找。
7.預設拒絕政策
7.1 AbortPolicy(抛出一個異常,預設的)
7.2 CallerRunsPolicy(交給線程池調用所在的線程進行處理)
7.3 DiscardOldestPolicy(丢棄隊列裡最老的任務,将目前這個任務繼續送出給線程池)
7.4 DiscardPolicy(直接丢棄任務)
PS:使用DiscardPolicy或者DiscardOldestPolicy,并且線程池飽和了的時候,我們将會直接丢棄任務,不會抛出任何異常。這個時候再來調用get方法是主線程就會一直等待子線程傳回結果,直到逾時抛出TimeoutException。
AQS 核心思想是,如果被請求的共享資源空閑,則将目前請求資源的線程設定為有效的工作線
程,并且将共享資源設定為鎖定狀态。如果被請求的共享資源被占用,那麼就需要一套線程阻塞
等待以及被喚醒時鎖配置設定的機制,這個機制 AQS 是用 CLH 隊列鎖實作的,即将暫時擷取不到鎖
的線程加入到隊列中。
CLH(Craig,Landin,and Hagersten)隊列是一個虛拟的雙向隊列(虛拟的雙向隊列即不存在 隊列執行個體,僅存在結點之間的關聯關系)。AQS 是将每條請求共享資源的線程封裝成一個 CLH 鎖隊列的一個結點(Node)來實作鎖的配置設定。

AQS 使用一個 int 成員變量來表示同步狀态,通過内置的 FIFO 隊列來完成擷取資源線程的排隊
工作。AQS 使用 CAS 對該同步狀态進行原子操作實作對其值的修改。
1、Exclusive(獨占): 隻有一個線程能執行,如 ReentrantLock 。又可分為公平鎖和非公平鎖:
公平鎖:按照線程在隊列中的排隊順序,先到者先拿到鎖
非公平鎖:當線程要擷取鎖時,無視隊列順序直接去搶鎖,誰搶到就是誰的
2、Share(共享): 多個線程可同時執行,如CountDownLatch 、 Semaphore 、 CountDownLatch 、 CyclicBarrier 、 ReadWriteLock 我們都會在後面講到。
ReentrantReadWriteLock 可以看成是組合式,因為 ReentrantReadWriteLock 也就是讀寫鎖允許多
個線程同時對某一資源進行讀。