吃透这JAVA并发十二核心,面试官都得对你刮目相看
1、HashMap
面試第一題必問的 HashMap,挺考驗Javaer的基礎功底的,別問為啥放在這,因為重要!HashMap具有如下特性:
常見HashMap考點:
2、ConcurrentHashMap
ConcurrentHashMap是多線程模式下常用的并發容器,它的實現在JDK7跟JDK8區別挺大的。
2.1 JDK7
JDK7中的 ConcurrentHashMap 使用?Segment?+?HashEntry?分段鎖實現并發,它的缺點是并發程度是由Segment?數組個數來決定的,并發度一旦初始化無法擴容,擴容的話只是HashEntry的擴容。
Segment?繼承自?ReentrantLock,在此扮演鎖的角色。可以理解為我們的每個Segment都是實現了Lock功能的HashMap。如果我們同時有多個Segment形成了Segment數組那我們就可以實現并發咯。
大致的put流程如下:
- ConcurrentHashMap底層大致實現?
ConcurrentHashMap允許多個修改操作并發進行,其關鍵在于使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的HashTable,只要多個修改操作發生在不同的段上就可以并發進行。
- ConcurrentHashMap在并發下的情況下如何保證取得的元素是最新的?
用于存儲鍵值對數據的HashEntry,在設計上它的成員變量value跟next都是volatile類型的,這樣就保證別的線程對value值的修改,get方法可以馬上看到,并且get的時候是不用加鎖的。
- ConcurrentHashMap的弱一致性體現在clear和get方法,原因在于沒有加鎖。
比如迭代器在遍歷數據的時候是一個Segment一個Segment去遍歷的,如果在遍歷完一個Segment時正好有一個線程在剛遍歷完的Segment上插入數據,就會體現出不一致性。clear也是一樣。get方法和containsKey方法都是遍歷對應索引位上所有節點,都是不加鎖來判斷的,如果是修改性質的因為可見性的存在可以直接獲得最新值,不過如果是新添加值則無法保持一致性。
- size?統計個數不準確
size方法比較有趣,先無鎖的統計所有的數據量看下前后兩次是否數據一樣,如果一樣則返回數據,如果不一樣則要把全部的segment進行加鎖,統計,解鎖。并且size方法只是返回一個統計性的數字。
2.2 JDK8
ConcurrentHashMap 在JDK8中拋棄了分段鎖,轉為用?CAS?+?synchronized,同時將HashEntry改為Node,還加入了紅黑樹的實現,主要還是看put的流程(如果看了擴容這塊,絕對可以好好吹逼一番)。
ConcurrentHashMap?是如果來做到高效并發安全?
- 讀操作
get方法中根本沒有使用同步機制,也沒有使用unsafe方法,所以讀操作是支持并發操作的。
- 寫操作
基本思路跟HashMap的寫操作類似,只不過用到了CAS?+?syn?實現加鎖,同時還涉及到擴容的操作。JDK8中鎖已經細化到?table[i]?了,數組位置不同可并發,位置相同則去幫忙擴容。
- 同步處理主要是通過syn和unsafe的硬件級別原子性這兩種方式完成
當我們對某個table[i]操作時候是用syn加鎖的。取數據的時候用的是unsafe硬件級別指令,直接獲取指定內存的最新數據。
3 、并發基礎知識
并發編程的出發點:充分利用CPU計算資源,多線程并不是一定比單線程快,要不為什么 Redis 6.0版本的核心操作指令仍然是單線程呢?對吧!
多線程跟單線程的性能都要具體任務具體分析,talk is cheap, show me the picture。
3.1 進程跟線程
進程:
進程是操作系統調用的最小單位,是系統進行資源分配和調度的獨立單位。
線程:
3.2 并行跟并發
并發:
concurrency?: 多線程操作同一個資源,單核CPU極速的切換運行多個任務
并行:
parallelism?:多個CPU同時使用,CPU多核 真正的同時執行
3.3 線程幾個狀態
Java中線程的狀態分為6種:
- 初始(New):
新創建了一個線程對象,但還沒有調用start()方法。
- 可運行(Runnable):
- 運行中(Running)
就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。這也是線程進入運行狀態的唯一的一種方式。
- 阻塞(Blocked):
阻塞狀態是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。
- 等待(Waiting) 跟 超時等待(Timed_Waiting):
- 終止(Terminated):
當線程正常運行結束或者被異常中斷后就會被終止。線程一旦終止了,就不能復生。
PS:
3.4. 阻塞與等待的區別
阻塞:
當一個線程試圖獲取對象鎖(非JUC庫中的鎖,即synchronized),而該鎖被其他線程持有,則該線程進入阻塞狀態。它的特點是使用簡單,由JVM調度器來決定喚醒自己,而不需要由另一個線程來顯式喚醒自己,不響應中斷。
等待:
當一個線程等待另一個線程通知調度器一個條件時,該線程進入等待狀態。它的特點是需要等待另一個線程顯式地喚醒自己,實現靈活,語義更豐富,可響應中斷。例如調用:Object.wait()、Thread.join()以及等待?Lock?或?Condition。
雖然?synchronized 和 JUC 里的 Lock 都實現鎖的功能,但線程進入的狀態是不一樣的。synchronized 會讓線程進入阻塞態,而 JUC 里的 Lock是用park()/unpark() 來實現阻塞/喚醒 的,會讓線程進入等待狀態。雖然等鎖時進入的狀態不一樣,但被喚醒后又都進入Runnable狀態,從行為效果來看又是一樣的。
3.5 yield 跟 sleep 區別
3.6 wait 跟 sleep 區別
wait?來自Object,sleep?來自?Thread
wait?釋放鎖,sleep?不釋放
wait?必須在同步代碼塊中,sleep?可以任意使用
wait?不需要捕獲異常,sleep?需捕獲異常
3.7 多線程實現方式
3.8 死鎖
死鎖是指兩個或兩個以上的線程互相持有對方所需要的資源,由于某些鎖的特性,比如syn使用下,一個線程持有一個資源,或者說獲得一個鎖,在該線程釋放這個鎖之前,其它線程是獲取不到這個鎖的,而且會一直死等下去,因此這便造成了死鎖。
面試官:你給我解釋下死鎖是什么,解釋好了我就錄用你。
應聘者:先發Offer,發了Offer我給你解釋什么是死鎖。
產生條件:
互斥條件:一個資源,或者說一個鎖只能被一個線程所占用,當一個線程首先獲取到這個鎖之后,在該線程釋放這個鎖之前,其它線程均是無法獲取到這個鎖的。
占有且等待:一個線程已經獲取到一個鎖,再獲取另一個鎖的過程中,即使獲取不到也不會釋放已經獲得的鎖。
不可剝奪條件:任何一個線程都無法強制獲取別的線程已經占有的鎖
循環等待條件:線程A拿著線程B的鎖,線程B拿著線程A的鎖。。
檢查:
1、jps -l 定位進程號
2、jstack 進程號找到死鎖問題
避免:
加鎖順序:線程按照相同的順序加鎖。
限時加鎖:線程獲取鎖的過程中限制一定的時間,如果給定時間內獲取不到,就算了,這需要用到Lock的一些API。
4、JMM
4.1 JMM由來
隨著CPU、內存、磁盤的高速發展,它們的訪問速度差別很大。為了提速就引入了L1、L2、L3三級緩存。以后程序運行獲取數據就是如下的步驟了。
這樣雖然提速了但是會導致緩存一致性問題跟內存可見性問題。同時編譯器跟CPU為了加速也引入了指令重排。指令重排的大致意思就是你寫的代碼運行運算結果會按照你看到的邏輯思維去運行,但是在JVM內部系統是智能化的會進行加速排序的。
1、編譯器優化的重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
2、指令級并行的重排序:現代處理器采用了指令級并行技術在不影響數據依賴性前提下重排。
3、內存系統的重排序:處理器使用緩存和讀/寫緩沖區 進程重排。
指令重排這種機制會導致有序性問題,而在并發編程時經常會涉及到線程之間的通信跟同步問題,一般說是可見性、原子性、有序性。這三個問題對應的底層就是?緩存一致性、內存可見性、有序性。
原子性:原子性就是指該操作是不可再分的。不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。在整個操作過程中不會被線程調度器中斷的操作,都可認為是原子性。比如 a = 1。
可見性:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。Java保證可見性可以認為通過volatile、synchronized、final來實現。
有序性:程序執行的順序按照代碼的先后順序執行,Java通過volatile、synchronized來保證。
為了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存模式下多線程程序讀寫操作行為的規范,既JMM模型,注意JMM只是一個約定概念,是用來保證效果一致的機制跟規范。它作用于工作內存和主存之間數據同步過程,規定了如何做數據同步以及什么時候做數據同步。
在JMM中,有兩條規定:
線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接從主內存中讀寫。不同線程之間無法訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成。
共享變量要實現可見性,必須經過如下兩個步驟:
把本地內存1中更新過的共享變量刷新到主內存中。把主內存中最新的共享變量的值更新到本地內存2中。
同時人們提出了內存屏障、happen-before、af-if-serial這三種概念來保證系統的可見性、原子性、有序性。
4.2 內存屏障
內存屏障是一種CPU指令,用于控制特定條件下的重排序和內存可見性問題。Java編譯器也會根據內存屏障的規則禁止重排序。Java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序,從而讓程序按我們預想的流程去執行。具有如下功能:
保證特定操作的執行順序。
影響某些數據(或則是某條指令的執行結果)的內存可見性。
在 volatile 中就用到了內存屏障,volatile 部分已詳細講述。
4.3 happen-before
因為有指令重排的存在會導致難以理解CPU內部運行規則,JDK用 happens-before 的概念來闡述操作之間的內存可見性。在 JMM 中如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系 。其中CPU的happens-before無需任何同步手段就可以保證的。
- 程序順序規則:一個線程中的每個操作,happens-before于該線程中的任意后續操作。
- 監視器鎖規則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。
- volatile變量規則:對一個volatile域的寫,happens-before于任意后續對這個volatile域的讀。
- 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。
- start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
- join()規則:如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
- 線程中斷規則:對線程interrupt方法的調用happens-before于被中斷線程的代碼檢測到中斷事件的發生。
4.4 af-if-serial
af-if-serial?的含義是不管怎么重排序(編譯器和處理器為了提高并行度),單線程環境下程序的執行結果不能被改變且必須正確。該語義使單線程環境下程序員無需擔心重排序會干擾他們,也無需擔心內存可見性問題。
5、volatile
volatile 關鍵字的引入可以保證變量的可見性,但是無法保證變量的原子性,比如a++這樣的是無法保證的。這里其實涉及到 JMM 的知識點,Java多線程交互是通過共享內存的方式實現的。當我們讀寫volatile變量時具有如下規則:
當寫一個volatile變量時,JMM會把該線程對應的本地中的共享變量值刷新到主內存。
當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。
volatile就會用到上面說到的內存屏障,目前有四種內存屏障:
volatile 原理:用volatile變量修飾的共享變量進行寫操作的時候會使用CPU提供的Lock前綴指令,在CPU級別的功能如下:
6、單例模式 DCL + volatile
6.1 標準單例模式
高頻考點單例模式:就是將類的構造函數進行private化,然后只留出一個靜態的Instance?函數供外部調用者調用。單例模式一般標準寫法是?DCL?+?volatile:
public class SingleDcl {private volatile static SingleDcl singleDcl; //保證可見性private SingleDcl(){}public static SingleDcl getInstance(){// 放置進入加鎖代碼,先判斷下是否已經初始化好了if(singleDcl == null) { // 類鎖 可能會出現 AB線程都在這卡著,A獲得鎖,B等待獲得鎖。synchronized (SingleDcl.class) { if(singleDcl == null) {// 如果A線程初始化好了,然后通過vloatile 將變量復雜給住線程。// 如果此時沒有singleDel === null,判斷 B進程 進來后還會再次執行 new 語句singleDcl = new SingleDcl();}}}return singleDcl;} }6.2 為什么用Volatile修飾
不用Volatile則代碼運行時可能存在指令重排,會導致線程一在運行時執行順序是 1-->2--> 4 就賦值給instance變量了,然后接下來再執行構造方法初始化。問題是如果構造方法初始化執行沒完成前?線程二進入發現instance != null,直接給線程二個半成品,加入volatile后底層會使用內存屏障強制按照你以為的執行。
單例模式幾乎是面試必考點,,一般有如下特性:
懶漢式:在需要用到對象時才實例化對象,正確的實現方式是 Double Check + Lock + volatile,解決了并發安全和性能低下問題,對內存要求非常高,那么使用懶漢式寫法。
餓漢式:在類加載時已經創建好該單例對象,在獲取單例對象時直接返回對象即可,對內存要求不高使用餓漢式寫法,因為簡單不易出錯,且沒有任何并發安全和性能問題。
枚舉式:Effective Java 這本書也列舉了使用枚舉,其代碼精簡,沒有線程安全問題,且?Enum?類內部防止反射和反序列化時破壞單例。
7、線程池
7.1 五分鐘了解線程池
老王是個深耕在帝都的一線碼農,辛苦一年掙了點錢,想把錢存儲到銀行卡里,拿錢去銀行辦理遇到了如下的遭遇
上面的這個流程幾乎就跟JDK線程池的大致流程類似,其中7大參數:
當線程池的任務緩存隊列已滿并且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會采取任務拒絕策略,一般有四大拒絕策略:
7.2 正確創建方式
使用Executors創建線程池可能會導致OOM。原因在于線程池中的BlockingQueue主要有兩種實現,分別是ArrayBlockingQueue?和?LinkedBlockingQueue。
正確創建線程池的方式就是自己直接調用ThreadPoolExecutor的構造函數來自己創建線程池。在創建的同時,給BlockQueue指定容量就可以了。
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));7.3 常見線程池
羅列幾種常見的 線程池創建 方式。
定長的線程池,有核心線程,核心線程的即為最大的線程數量,沒有非核心線程。 使用的無界的等待隊列是LinkedBlockingQueue。使用時候小心堵滿等待隊列。
創建單個線程數的線程池,它可以保證先進先出的執行順序
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
創建一個定長的線程池,而且支持定時的以及周期性的任務執行,支持定時及周期性任務執行
最原始跟常見的創建線程池的方式,它包含了 7 個參數、4種拒絕策略 可用。
7.4 線程池核心點
線程池 在工作中常用,面試也是必考點。關于線程池的細節跟使用在以前舉例過一個 銀行排隊 辦業務的例子了。線程池一般主要也無非就是下面幾個考點了:
8、ThreadLocal
ThreadLocal 可以簡單理解為線程本地變量,相比于 synchronized 是用空間來換時間的思想。他會在每個線程都創建一個副本,在線程之間通過訪問內部副本變量的形式做到了線程之間互相隔離。這里用到了 弱引用 知識點:
如果一個對象只具有弱引用,那么GC回收器在掃描到該對象時,無論內存充足與否,都會回收該對象的內存。
8.1 核心點
每個Thread內部都維護一個ThreadLocalMap字典數據結構,字典的Key值是ThreadLocal,那么當某個ThreadLocal對象不再使用(沒有其它地方再引用)時,每個已經關聯了此ThreadLocal的線程怎么在其內部的ThreadLocalMap里做清除此資源呢?JDK中的ThreadLocalMap沒有繼承java.util.Map類,而是自己實現了一套專門用來定時清理無效資源的字典結構。其內部存儲實體結構Entry<ThreadLocal, T>繼承自
java.lan.ref.WeakReference,這樣當ThreadLocal不再被引用時,因為弱引用機制原因,當jvm發現內存不足時,會自動回收弱引用指向的實例內存,即其線程內部的ThreadLocalMap會釋放其對ThreadLocal的引用從而讓jvm回收ThreadLocal對象。這里是重點強調下,回收的是Key?也就是ThreadLocal對象,而非整個Entry,所以線程變量中的值T對象還是在內存中存在的,所以內存泄漏的問題還沒有完全解決。
接著分析底層代碼會發現在調用ThreadLocal.get() 或者 ThreadLocal.set() 都會?定期回收無效的Entry?操作。
9、CAS
Compare And Swap:比較并交換,主要是通過處理器的指令來保證操作的原子性,它包含三個操作數:
V:變量內存地址
A:舊的預期值
B:準備設置的新值
當執行 CAS 指令時,只有當 V 對應的值等于 A 時才會用 B 去更新V的值,否則就不會執行更新操作。CAS 可能會帶來ABA問題、循環開銷過大問題、一個共享變量原子性操作的局限性。如何解決以前寫過,在此不再重復。
10、Synchronized
10.1 Synchronized 講解
Synchronized 是 JDK自帶的線程安全關鍵字,該關鍵字可以修飾實例方法、靜態方法、代碼塊三部分。該關鍵字可以保證互斥性、可見性、有序性(不解決重排)但保證有序性。
Syn 的底層其實是C++代碼寫的,JDK6前是重量級鎖,調用的時候涉及到用戶態跟內核態的切換,挺耗時的。JDK6之前?Doug Lea寫出了JUC包,可以方便地讓用于在用戶態實現鎖的使用,Syn的開發者被激發了斗志所以在JDK6后對Syn進行了各種性能升級。
10.2 Synchronized 底層
Syn里涉及到了 對象頭 包含對象頭、填充數據、實例變量。這里可以看一個美團面試題:
問題一:new Object()占多少字節
markword 8字節 + classpointer 4字節(默認用calssPointer壓縮) + padding 4字節 =?16字節如果沒開啟classpointer壓縮:markword 8字節 + classpointer 8字節 =?16字節
問題二:User (int id,String name) User u = new User(1,"李四")
markword 8字節 + 開啟classPointer壓縮后classpointer 4字節 + instance data int 4字節 + 開啟普通對象指針壓縮后String4字節 + padding 4 =?24字節
10.3 Synchronized 鎖升級
synchronized 鎖在JDK6以后有四種狀態,無鎖、偏向鎖、輕量級鎖、重量級鎖。這幾個狀態會隨著競爭狀態逐漸升級,鎖可以升級但不能降級,但是偏向鎖狀態可以被重置為無鎖狀態。大致升級過程如下:
鎖對比:
| 鎖狀態 | 優點 | 缺點 | 適用場景 |
| 偏向鎖 | 加鎖解鎖無需額外消耗,跟非同步方法時間相差納秒級別 | 如果競爭線程多,會帶來額外的鎖撤銷的消耗 | 基本沒有其他線程競爭的同步場景 |
| 輕量級鎖 | 競爭的線程不會阻塞而是在自旋,可提高程序響應速度 | 如果一直無法獲得會自旋消耗CPU | 少量線程競爭,持有鎖時間不長,追求響應速度 |
| 重量級鎖 | 線程競爭不會導致CPU自旋跟消耗CPU資源 | 線程阻塞,響應時間長 | 很多線程競爭鎖,切鎖持有時間長,追求吞吐量時候 |
10.4 Synchronized 無法禁止指令重排,卻能保證有序性
指令重排是程序運行時?解釋器?跟?CPU?自帶的加速手段,可能導致語句執行順序跟預想不一樣情況,但是無論如何重排 也必須遵循?as-if-serial。
避免重排的最簡單方法就是禁止處理器優化跟指令重排,比如 volatile 中用內存屏障實現,syn是關鍵字級別的排他且可重入鎖,當某個線程執行到一段被syn修飾的代碼之前,會先進行加鎖,執行完之后再進行解鎖。
當某段代碼被syn加鎖后跟解鎖前,其他線程是無法再次獲得鎖的,只有這條加鎖線程可以重復獲得該鎖。所以代碼在執行的時候是單線程執行的,這就滿足了as-if-serial語義,正是因為有了as-if-serial語義保證,單線程的有序性就天然存在了。
10.5 wait 虛假喚醒
虛假喚醒定義:
當一個條件滿足時,很多線程都被喚醒了,但只有其中部分是有用的喚醒,其它的喚醒是不對的,比如說買賣貨物,如果商品本來沒有貨物,所有消費者線程都在wait狀態卡頓呢。這時突然生產者進了一件商品,喚醒了所有掛起的消費者??赡軐е滤械南M者都繼續執行wait下面的代碼,出現錯誤調用。
虛假喚醒原因:
因為?if?只會執行一次,執行完會接著向下執行?if?下面的。而?while?不會,直到條件滿足才會向下執行?while下面的。
虛假喚醒 解決辦法:
在調用?wait?的時候要用?while?不能用?if。
10.6 notify()底層
synchronized?代碼塊通過?javap?生成的字節碼中包含monitorenter?和monitorexit?指令線程,執行?monitorenter?指令可以獲取對象的?monitor,而wait?方法通過調用?native?方法?wait(0)?實現,該注釋說:The current thread must own this object's monitor。
notify/notifyAll?調用時并不會真正釋放對象鎖,只是把等待中的線程喚醒然后放入到對象的鎖池中,但是鎖池中的所有線程都不會立馬運行,只有擁有鎖的線程運行完代碼塊釋放鎖,別的線程拿到鎖才可以運行。
public void test() {Object object = new Object();synchronized (object){object.notifyAll();while (true){// TODO 死循環會導致 無法釋放鎖。}} }11、AQS
11.1 高頻考點線程交替打印
目標是實現兩個線程交替打印,實現字母在前數字在后。你可以用信號量、Synchronized關鍵字跟Lock實現,這里用 ReentrantLock簡單實現:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class Main {private static Lock lock = new ReentrantLock();private static Condition c1 = lock.newCondition();private static Condition c2 = lock.newCondition();private static CountDownLatch count = new CountDownLatch(1);public static void main(String[] args) {String c = "ABCDEFGHI";char[] ca = c.toCharArray();String n = "123456789";char[] na = n.toCharArray();Thread t1 = new Thread(() -> {try {lock.lock();count.countDown();for(char caa : ca) {c1.signal();System.out.print(caa);c2.await();}c1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});Thread t2 = new Thread(() -> {try {count.await();lock.lock();for(char naa : na) {c2.signal();System.out.print(naa);c1.await();}c2.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});t1.start(); t2.start();} }11.2 AQS底層
上題我們用到了ReentrantLock、Condition?,但是它們的底層是如何實現的呢?其實他們是基于AQS 的?同步隊列?跟?等待隊列?實現的!
11.2.1 AQS 同步隊列
學 AQS 前?CAS?+?自旋?+?LockSupport?+?模板模式?必須會,目的是方便理解源碼,感覺比 Synchronized 簡單,因為是單純的?Java?代碼。個人理解 AQS 具有如下幾個特點:
ReentrantLock底層:
11.2.2 AQS 等待隊列
當我們調用 Condition 里的?await?跟?signal?時候底層其實是這樣走的。
12、線程思考
12.1. 變量建議使用棧封閉
所有的變量都是在方法內部聲明的,這些變量都處于棧封閉狀態。方法調用的時候會有一個棧楨,這是一個獨立的空間。在這個獨立空間創建跟使用則絕對是安全的,但是注意不要返回該變量哦!
12.2. 防止線程饑餓
優先級低的線程總是得不到執行機會,一般要保證資源充足、公平的分配資源、防止持有鎖的線程長時間執行。
12.3 開發步驟
多線程編程不要為了用而用,引入多線程后會引入額外的開銷。量應用程序性能一般:服務時間、延遲時間、吞吐量、可伸縮性。做應用的時候可以一般按照如下步驟:
阿姆達爾定律中 a為并行計算部分所占比例,n為并行處理結點個數:
12.4 影響性能因素
13、End
都看到這了,送你幾個高頻面試題吧。
總結
以上是生活随笔為你收集整理的吃透这JAVA并发十二核心,面试官都得对你刮目相看的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 银河麒麟下载链接
- 下一篇: window下jenkin安装,小白一看