151.關於成員變數和區域性變數能否線上程之間共享的總結:無論是成員變數還是區域性變數,它們均存在於虛擬機器棧中,而虛擬機器棧是執行緒私有的區域,因此它們均無法線上程之間共享
152.如果一個執行緒中的某個變數指向了一個物件,另外一個執行緒中的某個變數也指向了同一個物件,則如果其中一個執行緒對該物件的狀態的修改必然會影響到另外一個執行緒,畢竟它們指向的是同一個物件,此場景可以理解為兩個執行緒存在共享狀態或者說兩個執行緒存在公共狀態。(不需要區分指向物件的變數是成員變數還是區域性變數)
153.在使用lambda表示式編寫多執行緒程式碼時,不要被其中的語法所迷惑,lambda表示式的本質是一個匿名內部類,下面兩段程式碼是等價的
154.在使用執行緒池時,如果在最後沒有呼叫shutdown()方法銷燬執行緒池(即銷燬執行緒池中的所有執行緒),會發現jvm程序並沒有退出,這是因為jvm中存在使用者執行緒(即使所有執行緒任務已經執行完畢,但是程式執行路徑依然存在jvm程序中),所以jvm程序不會退出。jvm程序退出的條件是程序中所有的使用者執行緒均已經銷燬,即當jvm程序中不存在使用者執行緒時,jvm就會退出
156.當JVM程序中存在多個活躍執行緒時,會發現CPU的使用率接近100%,當有執行緒呼叫sleep()或者wait()阻塞時,會發現在阻塞期間CPU的使用率會下降
157.CAS配上失敗重試的方式可以保證更新操作的原子性
158.在處理併發問題時,資料安全問題是一個需要重點考慮的問題。那麼資料安全問題中的資料可以分為兩大類,一個是資料庫中的資料,一個是程式碼本身的資料
1)如果是資料庫中的資料,一般會直接在資料庫層面處理併發過程帶來的資料安全問題,例如可以透過加悲觀鎖;也可以在程式碼層面處理來保證資料安全
2)如果是程式碼本身的資料,那隻能透過Java中的併發機制來處理以確保資料安全
159.一般的應用程式可以分為兩大類:IO密集型和CPU密集型
1)IO密集型指的是應用程式在執行過程中頻繁與IO裝置進行互動,資料在記憶體和IO裝置之間複製過程中會存在IO阻塞的情況,而在IO阻塞時CPU處於空閒狀態,這對CPU資源是一種極大的浪費。因此IO密集型的應用程式由於存在IO阻塞無法最大程度利用CPU的資源。在此場景下,可以在應用程式中開闢多條程式執行路徑,透過執行緒不斷的上下文切換來充分利用CPU的資源(一個執行緒在IO阻塞期間可以把CPU時間片切回到另外一個執行緒,這樣就提升了CPU的使用率)
2)CPU密集型指的是應用程式基本都是取記憶體中的資料進行計算,很少與IO裝置進行資料互動,CPU基本一直處於忙碌狀態。在此場景下,如果應用程式中有多個執行緒頻繁地進行上下文切換則勢必會影響程式總體的效能
160.程序的本質是一個正在執行的程式,程式執行時系統會建立一個程序,並且給每個程序分配獨立的記憶體地址空間保證每個程序地址不會相互干擾
161.juc包中三個有代表性的工具類:
· java.util.concurrent.CountDownLatch
· java.util.concurrent.CyclicBarrier
· java.util.concurrent.Semaphore
162.當執行緒任務在執行過程中發出IO請求時,JVM會把當前執行緒設定為阻塞狀態(呼叫底層的park()方法將執行緒掛起),IO處理完畢之後JVM會把執行緒從阻塞狀態中解除
163.透過interrupt方式能夠使執行緒在終止時有機會去清理資源,而不是武斷地將執行緒終止,因此這種終止執行緒的方式顯然要更加安全和優雅
164.分析為什麼處於RUNNABLE狀態或者BLOCKED狀態的執行緒收到中斷請求後不需要丟擲中斷異常,而處於WAITING狀態或者TIMED_WAITING狀態或者被join()阻塞的執行緒收到中斷請求後需要丟擲中斷異常?
答:RUNNABLE狀態或者BLOCKED狀態的執行緒可以透過判斷中斷標誌位來決定程式下一步的走向;WAITING狀態或者TIMED_WAITING狀態的執行緒需要外界條件的觸發才能使執行緒進入RUNNABLE狀態,從而無法透過中斷標誌位來判斷執行緒是否收到中斷訊號。因此處於該種情況下的執行緒無法對中斷請求馬上做出響應,所以才需要丟擲一箇中斷異常,執行緒可以透過捕獲該中斷異常來決定程式下一步的走向
165.小結呼叫哪些方法可能會丟擲中斷異常:wait()、sleep()、join()。(如果執行緒因為呼叫這些方法被阻塞,在阻塞期間若有其他執行緒任務向該阻塞執行緒發出一箇中斷訊號,則該阻塞執行緒就會丟擲一箇中斷異常)
166.可重入鎖的最大意義就是避免死鎖
167.透過快取記憶體的儲存互動很好的解決了處理器與記憶體的速度矛盾,但也引入了一個新的問題,處理器之間的快取一致性問題
168.為了解決處理器之間快取不一致的問題,在CPU層面做了很多事情,主要提供了兩種解決辦法:匯流排鎖、快取鎖(基於快取一致性協議實現)
169.如果計算機存在多個處理器,每個處理器都有自己的快取記憶體,而快取記憶體中的資料都是來源於主記憶體中某些資料的複製,因此就需要透過快取一致性協議來保證各個快取記憶體中的資料是一致的
170.快取一致性協議比較常見的有MESI:表示共享資料(快取行)的四種狀態,分別是:
· Modify(修改)
· Exclusive(獨佔)
· Shared(共享)
· Invalid(無效)
· 場景:有一個共享變數i,當i在主記憶體、CPU1的快取記憶體和CPU2的快取記憶體中的值均為0時,此時i為Shared狀態;當i在主記憶體中的值為0、CPU1的快取記憶體中的值為1、CPU2的快取記憶體中的值為0時,此時i在CPU1中的狀態由Shared狀態變為Modify狀態,在CPU2中的狀態由Shared狀態變為Invalid狀態;當i在主記憶體中的值為0、CPU1的快取記憶體中的值為0、CPU2沒有快取i時,此時i為Exclusive狀態
171.從使用層面來說,AQS的功能分為兩種:獨佔和共享
· 獨佔鎖(鎖被一個執行緒獨佔),每次只能有一個執行緒持有鎖,比如ReentrantLock就是以獨佔方式實現的互斥鎖
· 共享鎖(鎖被多個執行緒共享),允許多個執行緒同時獲取鎖,併發訪問共享資源,比如ReentrantReadWriteLock
172.CAS是一種樂觀鎖機制,透過CPU層面實現
173.在Java層面,CAS相關的介面由sun.misc.Unsafe類提供,其中CAS相關的方法都是native方法
174.在高併發場景下,CAS不是很適用,假如有10個執行緒同時進行同一個CAS操作,由於只能有一個執行緒CAS成功,其他9個執行緒都會CAS失敗,所以會消耗大量的CPU資源,導致效能下降
175.偏向鎖是基於CAS實現,偏向鎖一般是關閉的,可以透過設定JVM引數關閉偏向鎖
176.ReentrantLock中定義了一個抽象的靜態內部類Sync繼承AbstractQueuedSynchronizer,還定義了另外兩個靜態內部類FairSync和NonfairSync,這兩個類繼承了Sync
177.一臺計算機能夠開啟的執行緒數畢竟是有限的,而且CPU的核心數畢竟也是有限的,開啟很多的執行緒並沒有多大意義,所以引入執行緒池來控制執行緒數量
178.CPU在切換執行執行緒的過程中,需要儲存當前執行緒的執行狀態並恢復要執行的執行緒狀態,這個過程就是執行緒的上下文切換
179.執行一個main方法之後,開啟Java VisualVM,找到對應的JVM程序點進去,點執行緒,可以發現實時執行緒數量是10個,其中守護執行緒數量是9個,則可以推出剩下的那個就是使用者執行緒,其實就是主執行緒。(也可以直接透過圖解看出來)
180.在建立執行緒池時用到的阻塞佇列本質上是一個存放執行緒任務的集合,即帶有阻塞功能的集合,阻塞的體現:如果集合元素滿了,則存任務的執行緒會阻塞,如果集合元素為空,則取任務的執行緒會阻塞
181.把該集合命名為阻塞佇列的原因:因為執行緒池的執行緒數量是有限的,假如執行緒池最大執行緒數量為100(JVM程序中最多有100條程式執行路徑),而執行緒任務數量為200,則會把剩下的100個執行緒任務存放到集合容器中,則這100個執行緒任務相當於進入阻塞狀態暫時無法執行,直到前面的執行緒任務陸續執行完畢
182.與執行緒池有關的幾個引數的理解:核心執行緒數量、最大執行緒數量、加開執行緒存活時間、阻塞佇列容量
1)隨著任務數量的增加,會增加活躍的執行緒數量
2)當活躍的執行緒數量 = 核心執行緒數量,此時不再增加活躍執行緒數量,而是往任務佇列裡堆積任務
3)當任務佇列堆滿了,隨著任務數量的增加,會在核心執行緒數的基礎上加開執行緒
4)直到活躍執行緒數量 = 最大執行緒數量,就不能增加執行緒了
5)如果某個時刻,任務數量 最大執行緒數量 + 佇列長度,則會丟擲異常RejectedExecutionException拒絕任務
183.物件是可以線上程之間傳遞的,比如在主執行緒中建立一個物件,可以把該物件傳遞給另外一個執行緒。例如在訂單推送的業務場景中,訂單物件在web容器建立的執行緒中建立,然後把這些物件傳遞給了自己建立的執行緒進行業務處理
184.在main方法中建立一個物件,同時在main方法中啟動兩個執行緒t0和t1,把該物件傳遞給t0和t1執行緒,如果t0執行緒和t1執行緒同時對該物件的狀態進行讀寫,程式的執行結果有兩種情況,一種是程式的執行出現了不正確的結果,則稱該物件的類是執行緒不安全的類,本段程式是執行緒不安全的程式;另外一種是程式的執行始終能得到正確的結果,則稱該物件的類是執行緒安全的類,本段程式是執行緒安全的程式
185.關於spring的單例和多例的執行緒安全問題
· spring單例是執行緒不安全的原因:單例意味著所有客戶端共享一個Bean,任何一個客戶端修改Bean的狀態都會影響到其他客戶端
· spring多例是執行緒安全的原因:多例意味著每個客戶端有各自的Bean,互不干擾
(可以透過程式碼驗證這一結論)
186.新增概念“執行執行緒任務需要的資料”,訂單推送場景中,dtoList中的item就是“執行執行緒任務需要的資料”
187.多個執行緒對該物件進行操作,始終能得到正確的結果,則該物件所屬的類就是執行緒安全的,否則就是執行緒不安全的。例如,ArrayList、HashMap,多個執行緒同時往ArrayList、HashMap中新增資料,有時候得到的結果並不是預期的結果,因此這兩個類是執行緒不安全的
188.阿里巴巴Java開發手冊中強制執行緒池不允許使用 Executors 去建立,而是透過 ThreadPoolExecutor 的方式,這有兩個好處:一個是更加明確執行緒池的執行規則,一個是規避資源耗盡的風險
189.從物件導向的角度看,main執行緒是執行緒中的Object,即main執行緒是所有執行緒的祖先,其他執行緒都是在main執行緒中或者main執行緒的“子執行緒”中建立和開啟的
190.感悟:多執行緒中兩個非常關鍵的基本點:多執行緒的作用、多執行緒帶來的問題。其他的所有的跟多執行緒有關的知識點幾乎都是圍繞這兩個基本點展開的,因此在學習多執行緒的過程中不能脫離這兩個基本點