成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java并發(fā)十二連招,你能接住嗎?

開發(fā) 后端
JDK7中的 ConcurrentHashMap 使用 Segment + HashEntry 分段鎖實(shí)現(xiàn)并發(fā),它的缺點(diǎn)是并發(fā)程度是由Segment 數(shù)組個(gè)數(shù)來決定的,并發(fā)度一旦初始化無法擴(kuò)容,擴(kuò)容的話只是HashEntry的擴(kuò)容。

[[360382]]

本文轉(zhuǎn)載自微信公眾號(hào)「sowhat1412」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系sowhat1412公眾號(hào)。

1、HashMap

 

面試第一題必問的 HashMap,挺考驗(yàn)Javaer的基礎(chǔ)功底的,別問為啥放在這,因?yàn)橹匾?HashMap具有如下特性:

  1. HashMap 的存取是沒有順序的。
  2. KV 均允許為 NULL。
  3. 多線程情況下該類安全,可以考慮用 HashTable。
  4. JDk8底層是數(shù)組 + 鏈表 + 紅黑樹,JDK7底層是數(shù)組 + 鏈表。
  5. 初始容量和裝載因子是決定整個(gè)類性能的關(guān)鍵點(diǎn),輕易不要?jiǎng)印?/li>
  6. HashMap是懶漢式創(chuàng)建的,只有在你put數(shù)據(jù)時(shí)候才會(huì) build。
  7. 單向鏈表轉(zhuǎn)換為紅黑樹的時(shí)候會(huì)先變化為雙向鏈表最終轉(zhuǎn)換為紅黑樹,切記雙向鏈表跟紅黑樹是共存的。
  8. 對(duì)于傳入的兩個(gè)key,會(huì)強(qiáng)制性的判別出個(gè)高低,目的是為了決定向左還是向右放置數(shù)據(jù)。
  9. 鏈表轉(zhuǎn)紅黑樹后會(huì)努力將紅黑樹的root節(jié)點(diǎn)和鏈表的頭節(jié)點(diǎn) 跟table[i]節(jié)點(diǎn)融合成一個(gè)。
  10. 在刪除的時(shí)候是先判斷刪除節(jié)點(diǎn)紅黑樹個(gè)數(shù)是否需要轉(zhuǎn)鏈表,不轉(zhuǎn)鏈表就跟RBT類似,找個(gè)合適的節(jié)點(diǎn)來填充已刪除的節(jié)點(diǎn)。
  11. 紅黑樹的root節(jié)點(diǎn)不一定跟table[i]也就是鏈表的頭節(jié)點(diǎn)是同一個(gè),三者同步是靠MoveRootToFront實(shí)現(xiàn)的。而HashIterator.remove()會(huì)在調(diào)用removeNode的時(shí)候movable=false。

常見HashMap考點(diǎn):

  1. HashMap原理,內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
  2. HashMap中的put、get、remove大致過程。
  3. HashMap中 hash函數(shù)實(shí)現(xiàn)。
  4. HashMap如何擴(kuò)容。
  5. HashMap幾個(gè)重要參數(shù)為什么這樣設(shè)定。
  6. HashMap為什么線程不安全,如何替換。
  7. HashMap在JDK7跟JDK8中的區(qū)別。
  8. HashMap中鏈表跟紅黑樹切換思路。
  9. JDK7中 HashMap環(huán)產(chǎn)生原理。

2、ConcurrentHashMap

ConcurrentHashMap 是多線程模式下常用的并發(fā)容器,它的實(shí)現(xiàn)在JDK7跟JDK8區(qū)別挺大的。

2.1 JDK7

JDK7中的 ConcurrentHashMap 使用 Segment + HashEntry 分段鎖實(shí)現(xiàn)并發(fā),它的缺點(diǎn)是并發(fā)程度是由Segment 數(shù)組個(gè)數(shù)來決定的,并發(fā)度一旦初始化無法擴(kuò)容,擴(kuò)容的話只是HashEntry的擴(kuò)容。

Segment 繼承自 ReentrantLock,在此扮演鎖的角色。可以理解為我們的每個(gè)Segment都是實(shí)現(xiàn)了Lock功能的HashMap。如果我們同時(shí)有多個(gè)Segment形成了Segment數(shù)組那我們就可以實(shí)現(xiàn)并發(fā)咯。

 

大致的put流程如下:

 

1.ConcurrentHashMap底層大致實(shí)現(xiàn)?

ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)。它使用了多個(gè)鎖來控制對(duì)hash表的不同部分進(jìn)行的修改。內(nèi)部使用段(Segment)來表示這些不同的部分,每個(gè)段其實(shí)就是一個(gè)小的HashTable,只要多個(gè)修改操作發(fā)生在不同的段上就可以并發(fā)進(jìn)行。

2.ConcurrentHashMap在并發(fā)下的情況下如何保證取得的元素是最新的?

用于存儲(chǔ)鍵值對(duì)數(shù)據(jù)的HashEntry,在設(shè)計(jì)上它的成員變量value跟next都是volatile類型的,這樣就保證別的線程對(duì)value值的修改,get方法可以馬上看到,并且get的時(shí)候是不用加鎖的。

3.ConcurrentHashMap的弱一致性體現(xiàn)在clear和get方法,原因在于沒有加鎖。

比如迭代器在遍歷數(shù)據(jù)的時(shí)候是一個(gè)Segment一個(gè)Segment去遍歷的,如果在遍歷完一個(gè)Segment時(shí)正好有一個(gè)線程在剛遍歷完的Segment上插入數(shù)據(jù),就會(huì)體現(xiàn)出不一致性。clear也是一樣。get方法和containsKey方法都是遍歷對(duì)應(yīng)索引位上所有節(jié)點(diǎn),都是不加鎖來判斷的,如果是修改性質(zhì)的因?yàn)榭梢娦缘拇嬖诳梢灾苯荧@得最新值,不過如果是新添加值則無法保持一致性。

4.size 統(tǒng)計(jì)個(gè)數(shù)不準(zhǔn)確

size方法比較有趣,先無鎖的統(tǒng)計(jì)所有的數(shù)據(jù)量看下前后兩次是否數(shù)據(jù)一樣,如果一樣則返回?cái)?shù)據(jù),如果不一樣則要把全部的segment進(jìn)行加鎖,統(tǒng)計(jì),解鎖。并且size方法只是返回一個(gè)統(tǒng)計(jì)性的數(shù)字。

2.2 JDK8

ConcurrentHashMap 在JDK8中拋棄了分段鎖,轉(zhuǎn)為用 CAS + synchronized,同時(shí)將HashEntry改為Node,還加入了紅黑樹的實(shí)現(xiàn),主要還是看put的流程(如果看了擴(kuò)容這塊,絕對(duì)可以好好吹逼一番)。

 

ConcurrentHashMap 是如果來做到高效并發(fā)安全?

1.讀操作

get方法中根本沒有使用同步機(jī)制,也沒有使用unsafe方法,所以讀操作是支持并發(fā)操作的。

2.寫操作

基本思路跟HashMap的寫操作類似,只不過用到了CAS + syn 實(shí)現(xiàn)加鎖,同時(shí)還涉及到擴(kuò)容的操作。JDK8中鎖已經(jīng)細(xì)化到 table[i] 了,數(shù)組位置不同可并發(fā),位置相同則去幫忙擴(kuò)容。

3.同步處理主要是通過syn和unsafe的硬件級(jí)別原子性這兩種方式完成

當(dāng)我們對(duì)某個(gè)table[i]操作時(shí)候是用syn加鎖的。

取數(shù)據(jù)的時(shí)候用的是unsafe硬件級(jí)別指令,直接獲取指定內(nèi)存的最新數(shù)據(jù)。

3 、并發(fā)基礎(chǔ)知識(shí)

并發(fā)編程的出發(fā)點(diǎn):充分利用CPU計(jì)算資源,多線程并不是一定比單線程快,要不為什么Redis6.0版本的核心操作指令仍然是單線程呢?對(duì)吧!

多線程跟單線程的性能都要具體任務(wù)具體分析,talk is cheap, show me the picture。

 

3.1 進(jìn)程跟線程

進(jìn)程:

進(jìn)程是操作系統(tǒng)調(diào)用的最小單位,是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立單位。

線程:

因?yàn)檫M(jìn)程的創(chuàng)建、銷毀、切換產(chǎn)生大量的時(shí)間和空間的開銷,進(jìn)程的數(shù)量不能太多,而線程是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,他是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度的最小單位。線程可以減少程序并發(fā)執(zhí)行時(shí)的時(shí)間和空間開銷,使得操作系統(tǒng)具有更好的并發(fā)性。

線程基本不擁有系統(tǒng)資源,只有一些運(yùn)行時(shí)必不可少的資源,比如程序計(jì)數(shù)器、寄存器和棧,進(jìn)程則占有堆、棧。線程,Java默認(rèn)有兩個(gè)線程 main 跟GC。Java是沒有權(quán)限開線程的,無法操作硬件,都是調(diào)用的 native 的 start0 方法 由 C++ 實(shí)現(xiàn)

3.2 并行跟并發(fā)

并發(fā):

concurrency : 多線程操作同一個(gè)資源,單核CPU極速的切換運(yùn)行多個(gè)任務(wù)

并行:

parallelism :多個(gè)CPU同時(shí)使用,CPU多核 真正的同時(shí)執(zhí)行

3.3 線程幾個(gè)狀態(tài)


 

Java中線程的狀態(tài)分為6種:

 

1.初始(New):

新創(chuàng)建了一個(gè)線程對(duì)象,但還沒有調(diào)用start()方法。

2.可運(yùn)行(Runnable):

調(diào)用線程的start()方法,此線程進(jìn)入就緒狀態(tài)。就緒狀態(tài)只是說你資格運(yùn)行,調(diào)度程序沒有給你CPU資源,你就永遠(yuǎn)是就緒狀態(tài)。

當(dāng)前線程sleep()方法結(jié)束,其他線程join()結(jié)束,等待用戶輸入完畢,某個(gè)線程拿到對(duì)象鎖,這些線程也將進(jìn)入就緒狀態(tài)。

當(dāng)前線程時(shí)間片用完了,調(diào)用當(dāng)前線程的yield()方法,當(dāng)前線程進(jìn)入就緒狀態(tài)。

鎖池里的線程拿到對(duì)象鎖后,進(jìn)入就緒狀態(tài)。

3.運(yùn)行中(Running)

就緒狀態(tài)的線程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)。這也是線程進(jìn)入運(yùn)行狀態(tài)的唯一的一種方式。

4.阻塞(Blocked):

阻塞狀態(tài)是線程阻塞在進(jìn)入synchronized關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài)。

5.等待(Waiting) 跟 超時(shí)等待(Timed_Waiting):

處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待被顯式地喚醒(通知或中斷),否則會(huì)處于無限期等待的狀態(tài)。

處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,不過無須無限期等待被其他線程顯示地喚醒,在達(dá)到一定時(shí)間后它們會(huì)自動(dòng)喚醒。

6.終止(Terminated):

當(dāng)線程正常運(yùn)行結(jié)束或者被異常中斷后就會(huì)被終止。線程一旦終止了,就不能復(fù)生。

PS:

調(diào)用 obj.wait 的線程需要先獲取 obj 的 monitor,wait會(huì)釋放 obj 的 monitor 并進(jìn)入等待態(tài)。所以 wait()/notify() 都要與 synchronized 聯(lián)用。

其實(shí)線程從阻塞/等待狀態(tài) 到 可運(yùn)行狀態(tài)都涉及到同步隊(duì)列跟等待隊(duì)列的,這點(diǎn)在 AQS 有講。

3.4. 阻塞與等待的區(qū)別

阻塞:

當(dāng)一個(gè)線程試圖獲取對(duì)象鎖(非JUC庫中的鎖,即synchronized),而該鎖被其他線程持有,則該線程進(jìn)入阻塞狀態(tài)。它的特點(diǎn)是使用簡(jiǎn)單,由JVM調(diào)度器來決定喚醒自己,而不需要由另一個(gè)線程來顯式喚醒自己,不響應(yīng)中斷。

等待:

當(dāng)一個(gè)線程等待另一個(gè)線程通知調(diào)度器一個(gè)條件時(shí),該線程進(jìn)入等待狀態(tài)。它的特點(diǎn)是需要等待另一個(gè)線程顯式地喚醒自己,實(shí)現(xiàn)靈活,語義更豐富,可響應(yīng)中斷。例如調(diào)用:Object.wait()、**Thread.join()**以及等待 Lock 或 Condition。

雖然 synchronized 和 JUC 里的 Lock 都實(shí)現(xiàn)鎖的功能,但線程進(jìn)入的狀態(tài)是不一樣的。synchronized 會(huì)讓線程進(jìn)入阻塞態(tài),而 JUC 里的 Lock是用park()/unpark() 來實(shí)現(xiàn)阻塞/喚醒 的,會(huì)讓線程進(jìn)入等待狀態(tài)。雖然等鎖時(shí)進(jìn)入的狀態(tài)不一樣,但被喚醒后又都進(jìn)入Runnable狀態(tài),從行為效果來看又是一樣的。

3.5 yield 跟 sleep 區(qū)別

  1. yield 跟 sleep 都能暫停當(dāng)前線程,都不會(huì)釋放鎖資源,sleep 可以指定具體休眠的時(shí)間,而 yield 則依賴 CPU 的時(shí)間片劃分。
  2. sleep方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì)。yield方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì)。
  3. 調(diào)用 sleep 方法使線程進(jìn)入等待狀態(tài),等待休眠時(shí)間達(dá)到,而調(diào)用我們的 yield方法,線程會(huì)進(jìn)入就緒狀態(tài),也就是sleep需要等待設(shè)置的時(shí)間后才會(huì)進(jìn)行就緒狀態(tài),而yield會(huì)立即進(jìn)入就緒狀態(tài)。
  4. sleep方法聲明會(huì)拋出 InterruptedException,而 yield 方法沒有聲明任何異常
  5. yield 不能被中斷,而 sleep 則可以接受中斷。
  6. sleep方法比yield方法具有更好的移植性(跟操作系統(tǒng)CPU調(diào)度相關(guān))

3.6 wait 跟 sleep 區(qū)別

1.來源不同

wait 來自O(shè)bject,sleep 來自 Thread

2.是否釋放鎖

wait 釋放鎖,sleep 不釋放

3.使用范圍

wait 必須在同步代碼塊中,sleep 可以任意使用

4.捕捉異常

wait 不需要捕獲異常,sleep 需捕獲異常

3.7 多線程實(shí)現(xiàn)方式

  1. 繼承 Thread,實(shí)現(xiàn)run方法
  2. 實(shí)現(xiàn) Runnable接口中的run方法,然后用Thread包裝下。Thread 是線程對(duì)象,Runnable 是任務(wù),線程啟動(dòng)的時(shí)候一定是對(duì)象。
  3. 實(shí)現(xiàn) Callable接口,F(xiàn)utureTask 包裝實(shí)現(xiàn)接口,Thread 包裝 FutureTask。Callable 與Runnable 的區(qū)別在于Callable的call方法有返回值,可以拋出異常,Callable有緩存。
  4. 通過線程池調(diào)用實(shí)現(xiàn)。
  5. 通過Spring的注解 @Async 實(shí)現(xiàn)。

3.8 死鎖

死鎖是指兩個(gè)或兩個(gè)以上的線程互相持有對(duì)方所需要的資源,由于某些鎖的特性,比如syn使用下,一個(gè)線程持有一個(gè)資源,或者說獲得一個(gè)鎖,在該線程釋放這個(gè)鎖之前,其它線程是獲取不到這個(gè)鎖的,而且會(huì)一直死等下去,因此這便造成了死鎖。

面試官:你給我解釋下死鎖是什么,解釋好了我就錄用你。

應(yīng)聘者:先發(fā)Offer,發(fā)了Offer我給你解釋什么是死鎖。

產(chǎn)生條件:

互斥條件:一個(gè)資源,或者說一個(gè)鎖只能被一個(gè)線程所占用,當(dāng)一個(gè)線程首先獲取到這個(gè)鎖之后,在該線程釋放這個(gè)鎖之前,其它線程均是無法獲取到這個(gè)鎖的。

占有且等待:一個(gè)線程已經(jīng)獲取到一個(gè)鎖,再獲取另一個(gè)鎖的過程中,即使獲取不到也不會(huì)釋放已經(jīng)獲得的鎖。

不可剝奪條件:任何一個(gè)線程都無法強(qiáng)制獲取別的線程已經(jīng)占有的鎖

循環(huán)等待條件:線程A拿著線程B的鎖,線程B拿著線程A的鎖。。

檢查:

1、jps -l 定位進(jìn)程號(hào)

2、jstack 進(jìn)程號(hào)找到死鎖問題

避免:

加鎖順序:線程按照相同的順序加鎖。

限時(shí)加鎖:線程獲取鎖的過程中限制一定的時(shí)間,如果給定時(shí)間內(nèi)獲取不到,就算了,這需要用到Lock的一些API。

4、JMM

4.1 JMM由來

隨著CPU、內(nèi)存、磁盤的高速發(fā)展,它們的訪問速度差別很大。為了提速就引入了L1、L2、L3三級(jí)緩存。以后程序運(yùn)行獲取數(shù)據(jù)就是如下的步驟了。

這樣雖然提速了但是會(huì)導(dǎo)致緩存一致性問題跟內(nèi)存可見性問題。同時(shí)編譯器跟CPU為了加速也引入了指令重排。指令重排的大致意思就是你寫的代碼運(yùn)行運(yùn)算結(jié)果會(huì)按照你看到的邏輯思維去運(yùn)行,但是在JVM內(nèi)部系統(tǒng)是智能化的會(huì)進(jìn)行加速排序的。

 

1、編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

2、指令級(jí)并行的重排序:現(xiàn)代處理器采用了指令級(jí)并行技術(shù)在不影響數(shù)據(jù)依賴性前提下重排。

3、內(nèi)存系統(tǒng)的重排序:處理器使用緩存和讀/寫緩沖區(qū) 進(jìn)程重排。

指令重排這種機(jī)制會(huì)導(dǎo)致有序性問題,而在并發(fā)編程時(shí)經(jīng)常會(huì)涉及到線程之間的通信跟同步問題,一般說是可見性、原子性、有序性。這三個(gè)問題對(duì)應(yīng)的底層就是 緩存一致性、內(nèi)存可見性、有序性。

原子性:原子性就是指該操作是不可再分的。不論是多核還是單核,具有原子性的量,同一時(shí)刻只能有一個(gè)線程來對(duì)它進(jìn)行操作。在整個(gè)操作過程中不會(huì)被線程調(diào)度器中斷的操作,都可認(rèn)為是原子性。比如 a = 1。

可見性:指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。Java保證可見性可以認(rèn)為通過volatile、synchronized、final來實(shí)現(xiàn)。

有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,Java通過volatile、synchronized來保證。

為了保證共享內(nèi)存的正確性(可見性、有序性、原子性),內(nèi)存模型定義了共享內(nèi)存模式下多線程程序讀寫操作行為的規(guī)范,既JMM模型,注意JMM只是一個(gè)約定概念,是用來保證效果一致的機(jī)制跟規(guī)范。它作用于工作內(nèi)存和主存之間數(shù)據(jù)同步過程,規(guī)定了如何做數(shù)據(jù)同步以及什么時(shí)候做數(shù)據(jù)同步。

在JMM中,有兩條規(guī)定:

 

線程對(duì)共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行,不能直接從主內(nèi)存中讀寫。

不同線程之間無法訪問其他線程工作內(nèi)存中的變量,線程間變量值的傳遞需要通過主內(nèi)存來完成。

共享變量要實(shí)現(xiàn)可見性,必須經(jīng)過如下兩個(gè)步驟:

把本地內(nèi)存1中更新過的共享變量刷新到主內(nèi)存中。

把主內(nèi)存中最新的共享變量的值更新到本地內(nèi)存2中。

同時(shí)人們提出了內(nèi)存屏障、happen-before、af-if-serial這三種概念來保證系統(tǒng)的可見性、原子性、有序性。

4.2 內(nèi)存屏障

內(nèi)存屏障 (Memory Barrier) 是一種CPU指令,用于控制特定條件下的重排序和內(nèi)存可見性問題。Java編譯器也會(huì)根據(jù)內(nèi)存屏障的規(guī)則禁止重排序。Java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來禁止特定類型的處理器重排序,從而讓程序按我們預(yù)想的流程去執(zhí)行。具有如下功能:

保證特定操作的執(zhí)行順序。

影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結(jié)果)的內(nèi)存可見性。

在 volatile 中就用到了內(nèi)存屏障,volatile部分已詳細(xì)講述。

4.3 happen-before

因?yàn)橛兄噶钪嘏诺拇嬖跁?huì)導(dǎo)致難以理解CPU內(nèi)部運(yùn)行規(guī)則,JDK用 happens-before 的概念來闡述操作之間的內(nèi)存可見性。在JMM 中如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見,那么這兩個(gè)操作之間必須要存在happens-before關(guān)系 。其中CPU的happens-before無需任何同步手段就可以保證的。

  • 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作。
  • 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖。
  • volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫,happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。
  • 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  • start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動(dòng)線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
  • join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
  • 線程中斷規(guī)則:對(duì)線程interrupt方法的調(diào)用happens-before于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。

4.4 af-if-serial

af-if-serial 的含義是不管怎么重排序(編譯器和處理器為了提高并行度),單線程環(huán)境下程序的執(zhí)行結(jié)果不能被改變且必須正確。該語義使單線程環(huán)境下程序員無需擔(dān)心重排序會(huì)干擾他們,也無需擔(dān)心內(nèi)存可見性問題。

5、volatile

volatile 關(guān)鍵字的引入可以保證變量的可見性,但是無法保證變量的原子性,比如 a++這樣的是無法保證的。這里其實(shí)涉及到JMM 的知識(shí)點(diǎn),Java多線程交互是通過共享內(nèi)存的方式實(shí)現(xiàn)的。當(dāng)我們讀寫volatile變量時(shí)具有如下規(guī)則:

當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地中的共享變量值刷新到主內(nèi)存。

當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。


 

 

volatile就會(huì)用到上面說到的內(nèi)存屏障,目前有四種內(nèi)存屏障:

  1. StoreStore屏障,保證普通寫不和volatile寫發(fā)生重排序
  2. StoreLoad屏障,保證volatile寫與后面可能的volatile讀寫不發(fā)生重排序
  3. LoadLoad屏障,禁止volatile讀與后面的普通讀重排序
  4. LoadStore屏障,禁止volatile讀和后面的普通寫重排序

volatile原理:用volatile變量修飾的共享變量進(jìn)行寫操作的時(shí)候會(huì)使用CPU提供的Lock前綴指令,在CPU級(jí)別的功能如下:

將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到 系統(tǒng)內(nèi)存。

這個(gè)寫回內(nèi)存的操作會(huì)告知在其他CPU你們拿到的變量是無效的下一次使用時(shí)候要重新共享內(nèi)存拿。

6、單例模式 DCL + volatile

6.1 標(biāo)準(zhǔn)單例模式

高頻考點(diǎn)單例模式:就是將類的構(gòu)造函數(shù)進(jìn)行private化,然后只留出一個(gè)靜態(tài)的Instance 函數(shù)供外部調(diào)用者調(diào)用。單例模式一般標(biāo)準(zhǔn)寫法是 DCL + volatile:

  1. public class SingleDcl { 
  2.     private volatile static SingleDcl singleDcl; //保證可見性 
  3.     private SingleDcl(){ 
  4.     } 
  5.     public static SingleDcl getInstance(){ 
  6.         // 放置進(jìn)入加鎖代碼,先判斷下是否已經(jīng)初始化好了 
  7.      if(singleDcl == null) {  
  8.      // 類鎖 可能會(huì)出現(xiàn) AB線程都在這卡著,A獲得鎖,B等待獲得鎖。 
  9.       synchronized (SingleDcl.class) {  
  10.     if(singleDcl == null) { 
  11.      // 如果A線程初始化好了,然后通過vloatile 將變量復(fù)雜給住線程。 
  12.      // 如果此時(shí)沒有singleDel === null,判斷 B進(jìn)程 進(jìn)來后還會(huì)再次執(zhí)行 new 語句 
  13.      singleDcl = new SingleDcl(); 
  14.     } 
  15.    } 
  16.      } 
  17.         return singleDcl; 
  18.     } 

6.2 為什么用Volatile修飾

 

不用Volatile則代碼運(yùn)行時(shí)可能存在指令重排,會(huì)導(dǎo)致線程一在運(yùn)行時(shí)執(zhí)行順序是 1-->2--> 4 就賦值給instance變量了,然后接下來再執(zhí)行構(gòu)造方法初始化。問題是如果構(gòu)造方法初始化執(zhí)行沒完成前 線程二進(jìn)入發(fā)現(xiàn)instance != null,直接給線程二個(gè)半成品,加入volatile后底層會(huì)使用內(nèi)存屏障強(qiáng)制按照你以為的執(zhí)行。

單例模式幾乎是面試必考點(diǎn),,一般有如下特性:

懶漢式:在需要用到對(duì)象時(shí)才實(shí)例化對(duì)象,正確的實(shí)現(xiàn)方式是 Double Check + Lock + volatile,解決了并發(fā)安全和性能低下問題,對(duì)內(nèi)存要求非常高,那么使用懶漢式寫法。

餓漢式:在類加載時(shí)已經(jīng)創(chuàng)建好該單例對(duì)象,在獲取單例對(duì)象時(shí)直接返回對(duì)象即可,對(duì)內(nèi)存要求不高使用餓漢式寫法,因?yàn)楹?jiǎn)單不易出錯(cuò),且沒有任何并發(fā)安全和性能問題。

枚舉式:Effective Java 這本書也列舉了使用枚舉,其代碼精簡(jiǎn),沒有線程安全問題,且 Enum 類內(nèi)部防止反射和反序列化時(shí)破壞單例。

7、線程池

7.1 五分鐘了解線程池

老王是個(gè)深耕在帝都的一線碼農(nóng),辛苦一年掙了點(diǎn)錢,想把錢存儲(chǔ)到銀行卡里,拿錢去銀行辦理遇到了如下的遭遇

老王銀行門口取號(hào)后發(fā)現(xiàn)有柜臺(tái)營業(yè)ing 但是沒人辦理業(yè)務(wù)就直接辦理了。

老王取號(hào)后發(fā)現(xiàn)柜臺(tái)上都有人在辦理,等待席有空地,去坐著等辦理去了。

老王取號(hào)后發(fā)現(xiàn)柜臺(tái)都有人辦理,等待席也人坐滿了,這個(gè)時(shí)候銀行經(jīng)理看到老王是老實(shí)人本著關(guān)愛老實(shí)人的態(tài)度,新開一個(gè)臨時(shí)窗口給他辦理了。

老王取號(hào)后發(fā)現(xiàn)柜臺(tái)都滿了,等待座位席也滿了,臨時(shí)窗口也人滿了。這個(gè)時(shí)候銀行經(jīng)理給出了若干解決策略。

  • 直接告知人太多不給你辦理了。
  • 采用冷暴力模式,也不給不辦理也不讓他走。
  • 經(jīng)理讓老王取嘗試跟座位席中最前面的人聊一聊看是否可以加塞,可以就辦理,不可以還是被踢走。
  • 經(jīng)理直接跟老王說誰讓你來的你找誰去我這辦理不了。

 

上面的這個(gè)流程幾乎就跟JDK線程池的大致流程類似,其中7大參數(shù):

  • 營業(yè)中的3個(gè)窗口對(duì)應(yīng)核心線程池?cái)?shù):corePoolSize
  • 銀行總的營業(yè)窗口數(shù)對(duì)應(yīng):maximumPoolSize
  • 打開的臨時(shí)窗口在多少時(shí)間內(nèi)無人辦理則關(guān)閉對(duì)應(yīng):keepAliveTime
  • 臨時(shí)窗口存貨時(shí)間單位:TimeUnit
  • 銀行里的等待座椅就是等待隊(duì)列:BlockingQueue
  • threadFactory 該參數(shù)在JDK中是 線程工廠,用來創(chuàng)建線程對(duì)象,一般不會(huì)動(dòng)。
  • 無法辦理的時(shí)候銀行給出的解決方法對(duì)應(yīng):RejectedExecutionHandler

當(dāng)線程池的任務(wù)緩存隊(duì)列已滿并且線程池中的線程數(shù)目達(dá)到maximumPoolSize,如果還有任務(wù)到來就會(huì)采取任務(wù)拒絕策略,一般有四大拒絕策略:

  • ThreadPoolExecutor.AbortPolicy :丟棄任務(wù),并拋出 RejectedExecutionException 異常。
  • ThreadPoolExecutor.CallerRunsPolicy:該任務(wù)被線程池拒絕,由調(diào)用 execute方法的線程執(zhí)行該任務(wù)。
  • ThreadPoolExecutor.DiscardOldestPolicy :拋棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)。
  • ThreadPoolExecutor.DiscardPolicy:丟棄任務(wù),也不拋出異常。

7.2 正確創(chuàng)建方式

使用Executors創(chuàng)建線程池可能會(huì)導(dǎo)致OOM。原因在于線程池中的BlockingQueue主要有兩種實(shí)現(xiàn),分別是ArrayBlockingQueue 和 LinkedBlockingQueue。

  • ArrayBlockingQueue 是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,必須設(shè)置容量。
  • LinkedBlockingQueue 是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個(gè)無邊界的阻塞隊(duì)列,最大長(zhǎng)度為Integer.MAX_VALUE,極易容易導(dǎo)致線程池OOM。

正確創(chuàng)建線程池的方式就是自己直接調(diào)用ThreadPoolExecutor的構(gòu)造函數(shù)來自己創(chuàng)建線程池。在創(chuàng)建的同時(shí),給BlockQueue指定容量就可以了。

  1. private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 
  2.         60L, TimeUnit.SECONDS, 
  3.         new ArrayBlockingQueue(10)); 

7.3 常見線程池

羅列幾種常見的線程池創(chuàng)建方式。

1.Executors.newFixedThreadPool

定長(zhǎng)的線程池,有核心線程,核心線程的即為最大的線程數(shù)量,沒有非核心線程。 使用的無界的等待隊(duì)列是LinkedBlockingQueue。使用時(shí)候小心堵滿等待隊(duì)列。

2.Executors.newSingleThreadExecutor

創(chuàng)建單個(gè)線程數(shù)的線程池,它可以保證先進(jìn)先出的執(zhí)行順序

3.Executors.newCachedThreadPool

創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

4.Executors.newScheduledThreadPool

創(chuàng)建一個(gè)定長(zhǎng)的線程池,而且支持定時(shí)的以及周期性的任務(wù)執(zhí)行,支持定時(shí)及周期性任務(wù)執(zhí)行

5.ThreadPoolExecutor

最原始跟常見的創(chuàng)建線程池的方式,它包含了 7 個(gè)參數(shù)、4種拒絕策略 可用。

7.4 線程池核心點(diǎn)


 

線程池 在工作中常用,面試也是必考點(diǎn)。關(guān)于線程池的細(xì)節(jié)跟使用在以前舉例過一個(gè) 銀行排隊(duì) 辦業(yè)務(wù)的例子了。線程池一般主要也無非就是下面幾個(gè)考點(diǎn)了:

 

  • 為什么用線程池。
  • 線程池的作用。
  • 7大重要參數(shù)。
  • 4大拒絕策略。
  • 常見線程池任務(wù)隊(duì)列,如何理解有界跟無界。
  • 常用的線程池模版。
  • 如何分配線程池個(gè)數(shù),IO密集型 還是 CPU密集型。
  • 設(shè)定一個(gè)線程池優(yōu)先級(jí)隊(duì)列,Runable 類要實(shí)現(xiàn)可對(duì)比功能,任務(wù)隊(duì)列使用優(yōu)先級(jí)隊(duì)列。

8、ThreadLocal

ThreadLocal 可以簡(jiǎn)單理解為線程本地變量,相比于 synchronized 是用空間來換時(shí)間的思想。他會(huì)在每個(gè)線程都創(chuàng)建一個(gè)副本,在線程之間通過訪問內(nèi)部副本變量的形式做到了線程之間互相隔離。這里用到了 弱引用 知識(shí)點(diǎn):

如果一個(gè)對(duì)象只具有弱引用,那么GC回收器在掃描到該對(duì)象時(shí),無論內(nèi)存充足與否,都會(huì)回收該對(duì)象的內(nèi)存。

8.1 核心點(diǎn)

每個(gè)Thread內(nèi)部都維護(hù)一個(gè)ThreadLocalMap字典數(shù)據(jù)結(jié)構(gòu),字典的Key值是ThreadLocal,那么當(dāng)某個(gè)ThreadLocal對(duì)象不再使用(沒有其它地方再引用)時(shí),每個(gè)已經(jīng)關(guān)聯(lián)了此ThreadLocal的線程怎么在其內(nèi)部的ThreadLocalMap里做清除此資源呢?JDK中的ThreadLocalMap沒有繼承java.util.Map類,而是自己實(shí)現(xiàn)了一套專門用來定時(shí)清理無效資源的字典結(jié)構(gòu)。其內(nèi)部存儲(chǔ)實(shí)體結(jié)構(gòu)Entry

接著分析底層代碼會(huì)發(fā)現(xiàn)在調(diào)用ThreadLocal.get() 或者 ThreadLocal.set() 都會(huì) 定期回收無效的Entry 操作。

 

9、CAS

Compare And Swap:比較并交換,主要是通過處理器的指令來保證操作的原子性,它包含三個(gè)操作數(shù):

V:變量?jī)?nèi)存地址

A:舊的預(yù)期值

B:準(zhǔn)備設(shè)置的新值

當(dāng)執(zhí)行CAS指令時(shí),只有當(dāng) V 對(duì)應(yīng)的值等于 A 時(shí)才會(huì)用 B 去更新V的值,否則就不會(huì)執(zhí)行更新操作。CAS可能會(huì)帶來ABA問題、循環(huán)開銷過大問題、一個(gè)共享變量原子性操作的局限性。如何解決以前寫過,在此不再重復(fù)。

10、Synchronized

10.1 Synchronized 講解

Synchronized 是 JDK自帶的線程安全關(guān)鍵字,該關(guān)鍵字可以修飾實(shí)例方法、靜態(tài)方法、代碼塊三部分。該關(guān)鍵字可以保證互斥性、可見性、有序性(不解決重排)但保證有序性。

Syn的底層其實(shí)是C++代碼寫的,JDK6前是重量級(jí)鎖,調(diào)用的時(shí)候涉及到用戶態(tài)跟內(nèi)核態(tài)的切換,挺耗時(shí)的。JDK6之前 Doug Lea寫出了JUC包,可以方便的讓用于在用戶態(tài)實(shí)現(xiàn)鎖的使用,Syn的開發(fā)者被激發(fā)了斗志所以在JDK6后對(duì)Syn進(jìn)行了各種性能升級(jí)。

10.2 Synchronized 底層

 

Syn里涉及到了 對(duì)象頭 包含對(duì)象頭、填充數(shù)據(jù)、實(shí)例變量。這里可以看一個(gè)美團(tuán)面試題:

問題一:new Object()占多少字節(jié)

  • markword 8字節(jié) + classpointer 4字節(jié)(默認(rèn)用calssPointer壓縮) + padding 4字節(jié) = 16字節(jié)
  • 如果沒開啟classpointer壓縮:markword 8字節(jié) + classpointer 8字節(jié) = 16字節(jié)

問題二:User (int id,String name) User u = new User(1,"李四")

markword 8字節(jié) + 開啟classPointer壓縮后classpointer 4字節(jié) + instance data int 4字節(jié) + 開啟普通對(duì)象指針壓縮后String4字節(jié) + padding 4 = 24字節(jié)

10.3 Synchronized 鎖升級(jí)

synchronized 鎖在JDK6以后有四種狀態(tài),無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)狀態(tài)逐漸升級(jí),鎖可以升級(jí)但不能降級(jí),但是偏向鎖狀態(tài)可以被重置為無鎖狀態(tài)。大致升級(jí)過程如下

 

鎖對(duì)比:

鎖狀態(tài) 優(yōu)點(diǎn) 缺點(diǎn) 適用場(chǎng)景
偏向鎖 加鎖解鎖無需額外消耗,跟非同步方法時(shí)間相差納秒級(jí)別 如果競(jìng)爭(zhēng)線程多,會(huì)帶來額外的鎖撤銷的消耗 基本沒有其他線程競(jìng)爭(zhēng)的同步場(chǎng)景
輕量級(jí)鎖 競(jìng)爭(zhēng)的線程不會(huì)阻塞而是在自旋,可提高程序響應(yīng)速度 如果一直無法獲得會(huì)自旋消耗CPU 少量線程競(jìng)爭(zhēng),持有鎖時(shí)間不長(zhǎng),追求響應(yīng)速度
重量級(jí)鎖 線程競(jìng)爭(zhēng)不會(huì)導(dǎo)致CPU自旋跟消耗CPU資源 線程阻塞,響應(yīng)時(shí)間長(zhǎng) 很多線程競(jìng)爭(zhēng)鎖,切鎖持有時(shí)間長(zhǎng),追求吞吐量時(shí)候

10.4 Synchronized 無法禁止指令重排,卻能保證有序性

指令重排是程序運(yùn)行時(shí) 解釋器 跟 CPU 自帶的加速手段,可能導(dǎo)致語句執(zhí)行順序跟預(yù)想不一樣情況,但是無論如何重排 也必須遵循 as-if-serial。

避免重排的最簡(jiǎn)單方法就是禁止處理器優(yōu)化跟指令重排,比如volatile中用內(nèi)存屏障實(shí)現(xiàn),syn是關(guān)鍵字級(jí)別的排他且可重入鎖,當(dāng)某個(gè)線程執(zhí)行到一段被syn修飾的代碼之前,會(huì)先進(jìn)行加鎖,執(zhí)行完之后再進(jìn)行解鎖。

當(dāng)某段代碼被syn加鎖后跟解鎖前,其他線程是無法再次獲得鎖的,只有這條加鎖線程可以重復(fù)獲得該鎖。所以代碼在執(zhí)行的時(shí)候是單線程執(zhí)行的,這就滿足了as-if-serial語義,正是因?yàn)橛辛薬s-if-serial語義保證,單線程的有序性就天然存在了。

10.5 wait 虛假喚醒

虛假喚醒定義:

當(dāng)一個(gè)條件滿足時(shí),很多線程都被喚醒了,但只有其中部分是有用的喚醒,其它的喚醒是不對(duì)的,

比如說買賣貨物,如果商品本來沒有貨物,所有消費(fèi)者線程都在wait狀態(tài)卡頓呢。這時(shí)突然生產(chǎn)者進(jìn)了一件商品,喚醒了所有掛起的消費(fèi)者。可能導(dǎo)致所有的消費(fèi)者都繼續(xù)執(zhí)行wait下面的代碼,出現(xiàn)錯(cuò)誤調(diào)用。

虛假喚醒原因:

因?yàn)?if 只會(huì)執(zhí)行一次,執(zhí)行完會(huì)接著向下執(zhí)行 if 下面的。而 while 不會(huì),直到條件滿足才會(huì)向下執(zhí)行 while下面的。

虛假喚醒 解決辦法:

在調(diào)用 wait 的時(shí)候要用 while 不能用 if。

10.6 notify()底層

1.為何wait跟notify必須要加synchronized鎖

synchronized 代碼塊通過 javap 生成的字節(jié)碼中包含monitorenter 和 monitorexit 指令線程,執(zhí)行 monitorenter 指令可以獲取對(duì)象的 monitor,而wait 方法通過調(diào)用 native 方法 wait(0) 實(shí)現(xiàn),該注釋說:The current thread must own this object's monitor。

2.notify 執(zhí)行后立馬喚醒線程嗎?

notify/notifyAll 調(diào)用時(shí)并不會(huì)真正釋放對(duì)象鎖,只是把等待中的線程喚醒然后放入到對(duì)象的鎖池中,但是鎖池中的所有線程都不會(huì)立馬運(yùn)行,只有擁有鎖的線程運(yùn)行完代碼塊釋放鎖,別的線程拿到鎖才可以運(yùn)行。

  1. public void test() 
  2.     Object object = new Object(); 
  3.     synchronized (object){ 
  4.         object.notifyAll(); 
  5.         while (true){ 
  6.           // TODO 死循環(huán)會(huì)導(dǎo)致 無法釋放鎖。 
  7.         } 
  8.     } 

11、AQS

11.1 高頻考點(diǎn)線程交替打印

目標(biāo)是實(shí)現(xiàn)兩個(gè)線程交替打印,實(shí)現(xiàn)字母在前數(shù)字在后。你可以用信號(hào)量、Synchronized關(guān)鍵字跟Lock實(shí)現(xiàn),這里用 ReentrantLock簡(jiǎn)單實(shí)現(xiàn):

  1. import java.util.concurrent.CountDownLatch; 
  2. import java.util.concurrent.locks.Condition; 
  3. import java.util.concurrent.locks.Lock; 
  4. import java.util.concurrent.locks.ReentrantLock; 
  5.   
  6. public class Main { 
  7.  private static Lock lock = new ReentrantLock(); 
  8.  private static Condition c1 = lock.newCondition(); 
  9.  private static Condition c2 = lock.newCondition(); 
  10.  private static CountDownLatch count = new CountDownLatch(1); 
  11.   
  12.  public static void main(String[] args) { 
  13.   String c = "ABCDEFGHI"
  14.   char[] ca = c.toCharArray(); 
  15.   String n = "123456789"
  16.   char[] na = n.toCharArray(); 
  17.   
  18.   Thread t1 = new Thread(() -> { 
  19.    try { 
  20.     lock.lock(); 
  21.     count.countDown(); 
  22.     for(char caa : ca) { 
  23.      c1.signal(); 
  24.      System.out.print(caa); 
  25.      c2.await(); 
  26.     } 
  27.     c1.signal(); 
  28.    } catch (InterruptedException e) { 
  29.     e.printStackTrace(); 
  30.    } finally { 
  31.     lock.unlock(); 
  32.    } 
  33.   }); 
  34.   
  35.   Thread t2 = new Thread(() -> { 
  36.    try { 
  37.     count.await(); 
  38.     lock.lock(); 
  39.     for(char naa : na) { 
  40.      c2.signal(); 
  41.      System.out.print(naa); 
  42.      c1.await(); 
  43.     } 
  44.     c2.signal(); 
  45.    } catch (InterruptedException e) { 
  46.     e.printStackTrace(); 
  47.    } finally { 
  48.     lock.unlock(); 
  49.    } 
  50.   }); 
  51.    
  52.   t1.start();  
  53.   t2.start(); 
  54.  } 

11.2 AQS底層

上題我們用到了ReentrantLock、Condition ,但是它們的底層是如何實(shí)現(xiàn)的呢?其實(shí)他們是基于AQS的 同步隊(duì)列 跟 等待隊(duì)列 實(shí)現(xiàn)的!

11.2.1 AQS 同步隊(duì)列

學(xué)AQS 前 CAS + 自旋 + LockSupport + 模板模式 必須會(huì),目的是方便理解源碼,感覺比 Synchronized 簡(jiǎn)單,因?yàn)槭菃渭兊? Java 代碼。個(gè)人理解AQS具有如下幾個(gè)特點(diǎn):

  1. 在AQS 同步隊(duì)列中 -1 表示線程在睡眠狀態(tài)
  2. 當(dāng)前Node節(jié)點(diǎn)線程會(huì)把前一個(gè)Node.ws = -1。當(dāng)前節(jié)點(diǎn)把前面節(jié)點(diǎn)ws設(shè)置為-1,你可以理解為:你自己能知道自己睡著了嗎?只能是別人看到了發(fā)現(xiàn)你睡眠了!
  3. 持有鎖的線程永遠(yuǎn)不在隊(duì)列中。
  4. 在AQS隊(duì)列中第二個(gè)才是最先排隊(duì)的線程。
  5. 如果是交替型任務(wù)或者單線程任務(wù),即使用了Lock也不會(huì)涉及到AQS 隊(duì)列。
  6. 不到萬不得已不要輕易park線程,很耗時(shí)的!所以排隊(duì)的頭線程會(huì)自旋的嘗試幾個(gè)獲取鎖。
  7. 并不是說 CAS 一定比SYN好,如果高并發(fā)執(zhí)行時(shí)間久 ,用SYN好, 因?yàn)镾YN底層用了wait() 阻塞后是不消耗CPU資源的。如果鎖競(jìng)爭(zhēng)不激烈說明自旋不嚴(yán)重 此時(shí)用CAS。
  8. 在AQS中也要盡可能避免調(diào)用CLH隊(duì)列,因?yàn)镃LH可能會(huì)調(diào)用到park,相對(duì)來耗時(shí)。

ReentrantLock底層:

 

11.2.2 AQS 等待隊(duì)列

當(dāng)我們調(diào)用 Condition 里的 await 跟 signal 時(shí)候底層其實(shí)是這樣走的。


 

 

12、線程思考

12.1. 變量建議使用棧封閉

所有的變量都是在方法內(nèi)部聲明的,這些變量都處于棧封閉狀態(tài)。方法調(diào)用的時(shí)候會(huì)有一個(gè)棧楨,這是一個(gè)獨(dú)立的空間。在這個(gè)獨(dú)立空間創(chuàng)建跟使用則絕對(duì)是安全的,但是注意不要返回該變量哦!

12.2. 防止線程饑餓

優(yōu)先級(jí)低的線程總是得不到執(zhí)行機(jī)會(huì),一般要保證資源充足、公平的分配資源、防止持有鎖的線程長(zhǎng)時(shí)間執(zhí)行。

12.3 開發(fā)步驟

多線程編程不要為了用而用,引入多線程后會(huì)引入額外的開銷。量應(yīng)用程序性能一般:服務(wù)時(shí)間、延遲時(shí)間、吞吐量、可伸縮性。做應(yīng)用的時(shí)候可以一般按照如下步驟:

  • 先確保保證程序的正確性跟健壯性,確實(shí)達(dá)不到性能要求再想如何提速。
  • 一定要以測(cè)試為基準(zhǔn)。
  • 一個(gè)程序中串行的部分永遠(yuǎn)是有的.
  • 裝逼利器:阿姆達(dá)爾定律 S=1/(1-a+a/n)

阿姆達(dá)爾定律中 a為并行計(jì)算部分所占比例,n為并行處理結(jié)點(diǎn)個(gè)數(shù):

  • 當(dāng)1-a=0時(shí),(即沒有串行,只有并行)最大加速比s=n;
  • 當(dāng)a=0時(shí)(即只有串行,沒有并行),最小加速比s=1;
  • 當(dāng)n無窮大時(shí),極限加速比s→ 1/(1-a),這就是加速比的上限。例如,若串行代碼占整個(gè)代碼的25%,則并行處理的總體性能不可能超過4。

12.4 影響性能因素

 

  • 縮小鎖的范圍,能鎖方法塊盡量不要鎖函數(shù)
  • 減少鎖的粒度跟鎖分段,比如ConcurrentHashMap的實(shí)現(xiàn)。
  • 讀多寫少時(shí)候用讀寫鎖,可提高十倍性能。
  • 用CAS操作來替換重型鎖。
  • 盡量用JDK自帶的常見并發(fā)容器,底層已經(jīng)足夠優(yōu)化了。

 

責(zé)任編輯:武曉燕 來源: sowhat1412
相關(guān)推薦

2020-09-02 07:00:42

ZooKeeper分布式

2021-03-09 12:25:08

區(qū)塊鏈資金比特幣

2022-01-14 08:12:39

瀏覽器面試問http

2020-02-11 20:01:44

面試ZooKeeper連問

2023-12-07 08:13:58

Java開發(fā)

2019-07-25 12:46:32

Java高并發(fā)編程語言

2021-02-02 08:21:28

網(wǎng)絡(luò)面試通信

2023-10-24 08:53:24

FutureTas并發(fā)編程

2014-12-01 10:01:19

2021-06-08 11:15:10

Redis數(shù)據(jù)庫命令

2014-03-04 17:56:58

iPhone 6

2019-09-23 08:46:04

零拷貝 CPU內(nèi)存

2020-07-01 07:52:07

Java并發(fā)容器

2013-11-28 15:25:37

Android4.4ART模式蘋果

2023-10-29 17:08:38

AQS線程

2021-08-22 14:20:15

iOS蘋果系統(tǒng)

2012-07-03 11:13:07

2018-12-20 09:30:59

分布式高并發(fā)多線程

2023-02-13 07:53:33

單調(diào)棧柱子非負(fù)整數(shù)

2015-02-12 17:58:08

微信SDK
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 在线看无码的免费网站 | 中文字幕一区在线观看视频 | 日本成人二区 | 日韩精品一区二区在线观看 | 国产精品成人av | 91麻豆产精品久久久久久夏晴子 | 欧美日韩视频在线 | 国产高潮好爽受不了了夜夜做 | 91精品国产91久久久久久三级 | 美女亚洲一区 | 日本免费黄色 | 日韩不卡在线观看 | 国产精品久久久久久吹潮 | 日韩视频成人 | 色约约视频 | 日韩电影在线一区 | 在线国产一区二区 | 成人精品一区二区三区中文字幕 | 久久精品99 | 国产精品国产a级 | 久久不卡| 国产久| 激情 婷婷| 午夜影院在线观看 | 国产欧美精品区一区二区三区 | 亚洲精品日韩一区二区电影 | 雨宫琴音一区二区在线 | 久久久久无码国产精品一区 | 99爱在线观看 | 日韩一区在线播放 | 国产精品揄拍一区二区 | 无码国模国产在线观看 | 91精品国产乱码久久久久久久 | a级免费视频| 国产在线网站 | japanhdxxxx裸体 | 九色 在线 | 中文字幕免费在线 | 91精品国产91久久久久游泳池 | 成人 在线 | 亚洲欧美日韩精品 |