Java併發程式設計藝術讀書筆記
1、多執行緒在CPU切換過程中,由於需要儲存執行緒之前狀態和載入新執行緒狀態,成為上下文切換,上下文切換會造成消耗系統記憶體。所以,可合理控制執行緒數量。 如何控制:
(1)使用ps -ef|grep appname,查詢appname的pid;如1111
(2)使用jstack 1111 > /home/ibethfy/dump1,將dump資訊追加到dump1
(3)使用grep java.lang.Thread.State /home/ibethfy/dump1 | awk '{print $2$3$4$5}' | sort | uniq -c,統計查詢執行緒狀態個數。分析是否WAITING的執行緒過多。
(4)檢視這些WAITING的執行緒時怎麼產生的,如某個服務建立執行緒池中執行緒過多,分析後對應修改。
(5)重啟後重新統計。
2、避免死鎖幾種常用方式
(1)避免一個執行緒同時獲取多個鎖。
(2)避免一個執行緒在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源。
(3)嘗試使用定時鎖,lock.tryLock(timeout)來替代內部鎖機制。
(4)對於資料庫鎖,加鎖和解鎖必須在同一個連線內,不然會造成解鎖失敗的情況。
3、volatile保證共享資源對其他執行緒的可見性。volatile修飾共享變數時,共享變數進行寫操作時,會在彙編中多一個lock字首的指令,該指定表示要將該變數從當前處理器的快取行資料寫到系統記憶體,寫會記憶體操作會使其他CPU快取了該記憶體地址的資料失效。其他CPU去獲取該資料時,會從系統記憶體重新獲取。
4、synchronized,JVM通過進入和退出Monitor物件來實現程式碼塊和方法同步。程式碼塊同步使用的是monitorenter和monitorexit來實現。在編譯時,會將這兩個指令分別插入到程式碼塊開始和結束(包括正常和異常結束),在執行這兩句指令時,會去進去和退出monitor物件。
5、鎖在哪?Java的鎖,是存在物件頭的Mark Word中。儲存一個鎖地址,指向C++裡面的一個ObjectMonitor物件,該物件儲存了當前佔用鎖的執行緒,等待的執行緒,該執行緒重入的次數,鎖狀態等欄位。
6、JMM,Java記憶體模型,用於控制Java執行緒之間的通訊,和計算機多執行緒模型類似,也是為每個執行緒分配了類似的記憶體快取,變數存在主記憶體中,要保證執行緒之間的通訊,必須要保證執行緒A將資料從本地快取重新整理到主記憶體,執行緒B識別到資料更新,從主記憶體中取得最新資料。JMM主要就是控制主記憶體和執行緒本地記憶體之間的互動等。
7、JMM模型中,主要是解決有序性(順序一致性),可見性,原子性。
(1)由於多執行緒中,每個執行緒有自己的本地快取,且在執行程式碼後,不知道何時回把快取重新整理到主記憶體,所以,執行緒的操作對其他執行緒是不可見的。這時不同執行緒獲取共享變數就會出現不一致的問題。
(2)指令的重排序:重排序是指編譯器和處理器為了優化程式效能而對指令序列進行重新排序的一種手段。資料依賴性:某一行程式碼依賴上一行程式碼,這樣不會進行這兩條程式碼的重排序,多執行緒系統不會考慮該問題。順序一致性:指令重排序後,最終執行結果要和順序執行結果一致。
(3)多執行緒之間進行了正確的資料同步,JMM保證多個執行緒的順序一致性。
(4)順序一致性模型:一個程式的操作必須按照程式順序執行。不管程式是否同步,所有執行緒都能按照單一的操作執行順序。即多個執行緒的操作可以並行執行,但對於單個執行緒來看,他的指令時順序執行的,且每條指令都對其他執行緒可見。
(5)JMM不保證順序一致性,即未同步的執行緒,執行順序是無序的,且可能對其他記憶體不可見。這個時候,就需要同步來實現順序一致性模型。synchronized修飾的程式碼塊,在獲得鎖後才能執行,從而保證了多個執行緒的整體順序執行,但同步塊區域性不依賴的指令,也可進行重排序(儘量給編譯器或者優化器優化提供入口)。為什麼JMM不保證未同步的執行緒的順序一致性呢?主要是要保證會禁止大量的編譯器等優化,嚴重影響效能,所以就提供同步方法來實現。
(6)JMM記憶體可見性的保證:單執行緒不會出現記憶體可見性問題,編譯器,runtime,處理器會共同保證期執行結果和順序一致性模型結果一致;多執行緒,正確同步的多執行緒將具有順序一致性,JMM通過限制編譯器和處理器的重排序來提供記憶體可見性的保證。未同步或未正確同步的多執行緒,JMM為他們提供了最小安全性的保證,執行緒讀取到的值,要麼是之前執行緒寫入的值,要麼是初始值。
(7)volatile,當寫volatile時,其他讀寫指令在其之後執行,保證了有序性;當寫時,會把新的快取中的值寫入到主記憶體;另一執行緒讀時,會將本地快取地址置為無效,從主記憶體中獲取該值。
(8)synchronized,當某執行緒獲得鎖時,其餘執行緒等待,保證了有序性。當某執行緒釋放鎖時,會將本地快取寫入主記憶體,當另一執行緒獲取鎖時,會將本地快取置為無效,從主記憶體獲取資料。所以保證了多執行緒的順序一致性和可見性。
(9)happens-before規則。
8、wait和notify:WaitThread首先獲取了物件的鎖,然後呼叫鎖物件的wait()方法,從而放棄了鎖並進入了物件的等待佇列WaitQueue中,進入等待狀態。由於WaitThread釋放了物件的鎖,NotifyThread隨後獲取了物件的鎖,並呼叫鎖物件的notify()方法,將WaitThread從WaitQueue移到SynchronizedQueue中,此時WaitThread的狀態變為阻塞狀態。NotifyThread釋放了鎖之後,WaitThread再次獲取到鎖並從wait()方法返回繼續執行。金典案例如下: synchronized(物件) {
while(條件不滿足) {
物件.wait();
}
對應的處理邏輯
}
synchronized(物件) {
改變條件
物件.notifyAll();
}
9、ThreadLocal:ThreadLocal,即執行緒變數,是一個以ThreadLocal執行緒物件為鍵、任意物件為值的儲存結構。這個結構被附帶線上程上,也就是說一個執行緒可以根據一個ThreadLocal物件查詢到繫結在這個執行緒上的一個值。
10、CountDownLatch:
(1)、初始化CountDownLatch cdl = new CountDownLatch(5);
(2)、同一執行緒或多個執行緒呼叫cdl.countDown(),使計數器減一,通過入參傳入cdl;等待執行緒呼叫cdl.await(),阻塞等待cdl計數器等於0後,執行。
11、CyclicBarrier:
(1)、初始化CyclicBarrier cb = new CyclicBarrier();
(2)、其餘執行緒呼叫cb.await(),告訴屏障我已到達,然後等待指定數量的執行緒都已到達後,然後統一往後執行。
(3)、建構函式可傳入另一執行緒,表示到達屏障的數量滿足後,優先執行該方法。
12、Semaphore:Semaphore(10)表示允許10個執行緒獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先執行緒使用Semaphore的acquire()方法獲取一個許可證,使用完之後呼叫release()方法歸還許可證。還可以用tryAcquire()方法嘗試獲取許可證。
13、Exchanger:用於執行緒間資料交換。
14、執行緒池技術:好處
(1)降低系統消耗,重複利用執行緒
(2)提高響應速度,不用重新建立執行緒
(3)提高可管理性,可以實現對執行緒的管理。
執行緒池流程:
(1)新來一個任務,如果沒有超過核心執行緒數,則建立一個核心執行緒,執行任務,不管是否有閒置的核心執行緒,也會建立新的核心執行緒執行任務。
(2)如果核心執行緒都處於執行狀態,且數量已達上限,則判斷工作佇列是否已滿,如果未滿,則將任務加入工作佇列,排隊等待核心執行緒空閒後執行。
(3)如果核心執行緒都在執行任務和工作佇列都滿了,則建立新的額外執行緒來執行任務。(額外執行緒由最大執行緒決定,且無用後會被回收)
(4)額外執行緒也滿了且都在執行的話,就採用拒絕策略拒絕請求。
執行緒池建立引數:
(1)corePoolSize,核心執行緒數量。
(2)runnableTaskQueue:任務佇列,有ArrayBlockingQueue,基於陣列的有界阻塞佇列,FIFO;LinkedBlockingQueue,基於連結串列實現的阻塞佇列,FIFO,吞吐量通常高於ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()使用了這個佇列;SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個佇列;
(3)maximumPoolSize:執行緒池最大執行緒數,執行緒池允許建立的最大執行緒數。如果佇列滿了,並且已建立的執行緒數小於最大執行緒數,則執行緒池會再建立新的執行緒執行任務。值得注意的是,如果使用了無界的任務佇列這個引數就沒什麼效果。
(4)ThreadFactory:用於設定建立執行緒的工廠;
(5)RejectedExecutionHandler(飽和策略):當佇列和執行緒池都滿了,說明執行緒池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略預設情況下是AbortPolicy,表示無法處理新任務時丟擲異常。以下四種策略: ·AbortPolicy:直接丟擲異常。 ·CallerRunsPolicy:只用呼叫者所線上程來執行任務。 ·DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。 ·DiscardPolicy:不處理,丟棄掉
(6)keepAliveTime(執行緒活動保持時間):執行緒池的工作執行緒空閒後(非核心執行緒會回收,allowCoreThreadTimeOut(true),設定這個屬性,核心執行緒在超時未用後,也會被回收),保持存活的時間。所以,如果任務很多,並且每個任務執行的時間比較短,可以調大時間,提高執行緒的利用率。
(7)TimeUnit(執行緒活動保持時間的單位);