干货:Java并发编程必懂知识点解析
本文大綱
1.并發(fā)編程三要素
原子性
原子,即一個(gè)不可再被分割的顆粒。在Java中原子性指的是一個(gè)或多個(gè)操作要么全部執(zhí)行成功要么全部執(zhí)行失敗。
有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。(處理器可能會(huì)對(duì)指令進(jìn)行重排序)
可見(jiàn)性
當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),如果其中一個(gè)線程對(duì)其作了修改,其他線程能立即獲取到最新的值。
2. 線程的五大狀態(tài)
創(chuàng)建狀態(tài)
當(dāng)用 new 操作符創(chuàng)建一個(gè)線程的時(shí)候
就緒狀態(tài)
調(diào)用 start 方法,處于就緒狀態(tài)的線程并不一定馬上就會(huì)執(zhí)行 run 方法,還需要等待CPU的調(diào)度
運(yùn)行狀態(tài)
CPU 開(kāi)始調(diào)度線程,并開(kāi)始執(zhí)行 run 方法
阻塞狀態(tài)
線程的執(zhí)行過(guò)程中由于一些原因進(jìn)入阻塞狀態(tài)
比如:調(diào)用 sleep 方法、嘗試去得到一個(gè)鎖等等
死亡狀態(tài)
run 方法執(zhí)行完 或者 執(zhí)行過(guò)程中遇到了一個(gè)異常
3.悲觀鎖與樂(lè)觀鎖
悲觀鎖 :每次操作都會(huì)加鎖,會(huì)造成線程阻塞。
樂(lè)觀鎖 :每次操作不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止,不會(huì)造成線程阻塞。
4.線程之間的協(xié)作
4.1 wait/notify/notifyAll
這一組是 Object 類的方法
需要注意的是:這三個(gè)方法都必須在同步的范圍內(nèi)調(diào)用
wait
阻塞當(dāng)前線程,直到 notify 或者 notifyAll 來(lái)喚醒
wait有三種方式的調(diào)用wait()必要要由 notify 或者 notifyAll 來(lái)喚醒wait(longtimeout)在指定時(shí)間內(nèi),如果沒(méi)有notify或notifAll方法的喚醒,也會(huì)自動(dòng)喚醒。wait(longtimeout,longnanos)本質(zhì)上還是調(diào)用一個(gè)參數(shù)的方法publicfinalvoidwait(longtimeout,intnanos)throwsInterruptedException{if(timeout <0) {thrownewIllegalArgumentException("timeout value is negative");}if(nanos <0|| nanos >999999) {thrownewIllegalArgumentException("nanosecond timeout value out of range");}if(nanos >0) {timeout++;}wait(timeout);}
notify
只能喚醒一個(gè)處于 wait 的線程
notifyAll
喚醒全部處于 wait 的線程
4.2 sleep/yield/join
這一組是 Thread 類的方法
sleep
讓當(dāng)前線程暫停指定時(shí)間,只是讓出CPU的使用權(quán),并不釋放鎖
yield
暫停當(dāng)前線程的執(zhí)行,也就是當(dāng)前CPU的使用權(quán),讓其他線程有機(jī)會(huì)執(zhí)行,不能指定時(shí)間。會(huì)讓當(dāng)前線程從運(yùn)行狀態(tài)轉(zhuǎn)變?yōu)榫途w狀態(tài),此方法在生產(chǎn)環(huán)境中很少會(huì)使用到,官方在其注釋中也有相關(guān)的說(shuō)明
/*** A hinttotheschedulerthatthecurrent threadiswillingtoyield*itscurrent useofa processor. The schedulerisfreetoignore this* hint.**
Yieldisa heuristic attempttoimprove relative progression*betweenthreadsthatwould otherwiseover-utilise a CPU. Its use* should be combinedwithdetailed profilingandbenchmarkingto* ensurethatitactually hasthedesired effect.**
Itisrarely appropriatetouse this method. It may be useful*fordebuggingortesting purposes,whereitmay helptoreproduce* bugs duetorace conditions. It may also be useful when designing* concurrency control constructs suchastheonesinthe* {@link java.util.concurrent.locks} package.*/
join
等待調(diào)用 join 方法的線程執(zhí)行結(jié)束,才執(zhí)行后面的代碼
其調(diào)用一定要在 start 方法之后(看源碼可知)
使用場(chǎng)景:當(dāng)父線程需要等待子線程執(zhí)行結(jié)束才執(zhí)行后面內(nèi)容或者需要某個(gè)子線程的執(zhí)行結(jié)果會(huì)用到 join 方法
5.valitate 關(guān)鍵字
5.1 定義
java編程語(yǔ)言允許線程訪問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。Java語(yǔ)言提供了volatile,在某些情況下比鎖更加方便。如果一個(gè)字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的。
valitate是輕量級(jí)的synchronized,不會(huì)引起線程上下文的切換和調(diào)度,執(zhí)行開(kāi)銷更小。
5.2 原理
1. 使用volitate修飾的變量在匯編階段,會(huì)多出一條lock前綴指令
2. 它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成
3. 它會(huì)強(qiáng)制將對(duì)緩存的修改操作立即寫入主存
4. 如果是寫操作,它會(huì)導(dǎo)致其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效
5.3 作用
內(nèi)存可見(jiàn)性
多線程操作的時(shí)候,一個(gè)線程修改了一個(gè)變量的值 ,其他線程能立即看到修改后的值
防止重排序
即程序的執(zhí)行順序按照代碼的順序執(zhí)行(處理器為了提高代碼的執(zhí)行效率可能會(huì)對(duì)代碼進(jìn)行重排序)
并不能保證操作的原子性(比如下面這段代碼的執(zhí)行結(jié)果一定不是100000)
publicclasstestValitate{publicvolatileintinc =0;publicvoidincrease(){inc = inc +1;}publicstaticvoidmain(String[] args){final testValitate test =newtestValitate();for(inti =0; i <100; i++) {newThread() {publicvoidrun(){for(intj =0; j <1000; j++)test.increase();}}.start();}while(Thread.activeCount() >2) {//保證前面的線程都執(zhí)行完Thread.yield();}System.out.println(test.inc);}}
6. synchronized 關(guān)鍵字
確保線程互斥的訪問(wèn)同步代碼
6.1 定義
synchronized 是JVM實(shí)現(xiàn)的一種鎖,其中鎖的獲取和釋放分別是
monitorenter 和 monitorexit 指令,該鎖在實(shí)現(xiàn)上分為了偏向鎖、輕量級(jí)鎖和重量級(jí)鎖,其中偏向鎖在 java1.6 是默認(rèn)開(kāi)啟的,輕量級(jí)鎖在多線程競(jìng)爭(zhēng)的情況下會(huì)膨脹成重量級(jí)鎖,有關(guān)鎖的數(shù)據(jù)都保存在對(duì)象頭中
6.2 原理
加了 synchronized 關(guān)鍵字的代碼段,生成的字節(jié)碼文件會(huì)多出 monitorenter 和 monitorexit 兩條指令(利用javap -verbose 字節(jié)碼文件可看到關(guān),關(guān)于這兩條指令的文檔如下:
monitorenter
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
monitorexit
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
加了 synchronized 關(guān)鍵字的方法,生成的字節(jié)碼文件中會(huì)多一個(gè) ACC_SYNCHRONIZED 標(biāo)志位,當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無(wú)法再獲得同一個(gè)monitor對(duì)象。 其實(shí)本質(zhì)上沒(méi)有區(qū)別,只是方法的同步是一種隱式的方式來(lái)實(shí)現(xiàn),無(wú)需通過(guò)字節(jié)碼來(lái)完成。
6.3 關(guān)于使用
修飾普通方法
同步對(duì)象是實(shí)例對(duì)象
修飾靜態(tài)方法
同步對(duì)象是類本身
修飾代碼塊
可以自己設(shè)置同步對(duì)象
6.4 缺點(diǎn)
會(huì)讓沒(méi)有得到鎖的資源進(jìn)入Block狀態(tài),爭(zhēng)奪到資源之后又轉(zhuǎn)為Running狀態(tài),這個(gè)過(guò)程涉及到操作系統(tǒng)用戶模式和內(nèi)核模式的切換,代價(jià)比較高。Java1.6為 synchronized 做了優(yōu)化,增加了從偏向鎖到輕量級(jí)鎖再到重量級(jí)鎖的過(guò)度,但是在最終轉(zhuǎn)變?yōu)橹亓考?jí)鎖之后,性能仍然較低。
7. CAS
AtomicBoolean,AtomicInteger,AtomicLong以及 Lock 相關(guān)類等底層就是用 CAS實(shí)現(xiàn)的,在一定程度上性能比 synchronized 更高。想要了解各大互聯(lián)網(wǎng)公司2018最新并發(fā)編程面試題的,可以加群:650385180,面試題以及答案在群的共享區(qū)。
7.1 什么是CAS
CAS全稱是Compare And Swap,即比較替換,是實(shí)現(xiàn)并發(fā)應(yīng)用到的一種技術(shù)。操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。 如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值 。否則,處理器不做任何操作。
7.2 為什么會(huì)有CAS
如果只是用 synchronized 來(lái)保證同步會(huì)存在以下問(wèn)題
synchronized 是一種悲觀鎖,在使用上會(huì)造成一定的性能問(wèn)題。在多線程競(jìng)爭(zhēng)下,加鎖、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí),引起性能問(wèn)題。一個(gè)線程持有鎖會(huì)導(dǎo)致其它所有需要此鎖的線程掛起。
7.3 實(shí)現(xiàn)原理
Java不能直接的訪問(wèn)操作系統(tǒng)底層,是通過(guò)native方法(JNI)來(lái)訪問(wèn)。CAS底層通過(guò)Unsafe類實(shí)現(xiàn)原子性操作。
7.4 存在的問(wèn)題
ABA問(wèn)題
什么是ABA問(wèn)題?比如有一個(gè) int 類型的值 N 是 1
此時(shí)有三個(gè)線程想要去改變它:
線程A :希望給 N 賦值為 2
線程B: 希望給 N 賦值為 2
線程C: 希望給 N 賦值為 1
此時(shí)線程A和線程B同時(shí)獲取到N的值1,線程A率先得到系統(tǒng)資源,將 N 賦值為 2,線程 B 由于某種原因被阻塞住,線程C在線程A執(zhí)行完后得到 N 的當(dāng)前值2
此時(shí)的線程狀態(tài)
線程A成功給 N 賦值為2
線程B獲取到 N 的當(dāng)前值 1 希望給他賦值為 2,處于阻塞狀態(tài)
線程C獲取當(dāng)好 N 的當(dāng)前值 2 希望給他賦值為1
然后線程C成功給N賦值為1
最后線程B得到了系統(tǒng)資源,又重新恢復(fù)了運(yùn)行狀態(tài),在阻塞之前線程B獲取到的N的值是1,執(zhí)行compare操作發(fā)現(xiàn)當(dāng)前N的值與獲取到的值相同(均為1),成功將N賦值為了2。
在這個(gè)過(guò)程中線程B獲取到N的值是一個(gè)舊值,雖然和當(dāng)前N的值相等,但是實(shí)際上N的值已經(jīng)經(jīng)歷了一次 1到2到1的改變
上面這個(gè)例子就是典型的ABA問(wèn)題
怎樣去解決ABA問(wèn)題
給變量加一個(gè)版本號(hào)即可,在比較的時(shí)候不僅要比較當(dāng)前變量的值 還需要比較當(dāng)前變量的版本號(hào)。Java中AtomicStampedReference 就解決了這個(gè)問(wèn)題
循環(huán)時(shí)間長(zhǎng)開(kāi)銷大
在并發(fā)量比較高的情況下,如果許多線程反復(fù)嘗試更新某一個(gè)變量,卻又一直更新不成功,循環(huán)往復(fù),會(huì)給CPU帶來(lái)很大的壓力。
CAS只能保證一個(gè)共享變量的原子操作
8. AbstractQueuedSynchronizer(AQS)
AQS抽象的隊(duì)列式同步器,是一種基于狀態(tài)(state)的鏈表管理方式。state 是用CAS去修改的。它是 java.util.concurrent 包中最重要的基石,要學(xué)習(xí)想學(xué)習(xí) java.util.concurrent 包里的內(nèi)容這個(gè)類是關(guān)鍵。 ReentrantLock、CountDownLatcher、Semaphore 實(shí)現(xiàn)的原理就是基于AQS。想知道他怎么實(shí)現(xiàn)以及實(shí)現(xiàn)原理 可以參看這篇文章 https://www.cnblogs.com/waterystone/p/4920797.html
9. Future
在并發(fā)編程我們一般使用Runable去執(zhí)行異步任務(wù),然而這樣做我們是不能拿到異步任務(wù)的返回值的,但是使用Future 就可以。使用Future很簡(jiǎn)單,只需把Runable換成FutureTask即可。使用上比較簡(jiǎn)單,這里不多做介紹。
10. 線程池
如果我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程,雖然簡(jiǎn)單,但是存在很大的問(wèn)題。如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。線程池通過(guò)復(fù)用可以大大減少線程頻繁創(chuàng)建與銷毀帶來(lái)的性能上的損耗。
Java中線程池的實(shí)現(xiàn)類 ThreadPoolExecutor,其構(gòu)造函數(shù)的每一個(gè)參數(shù)的含義在注釋上已經(jīng)寫得很清楚了,這里幾個(gè)關(guān)鍵參數(shù)可以再簡(jiǎn)單說(shuō)一下
corePoolSize :核心線程數(shù)即一直保留在線程池中的線程數(shù)量,即使處于閑置狀態(tài)也不會(huì)被銷毀。要設(shè)置 allowCoreThreadTimeOut 為 true,才會(huì)被銷毀。
maximumPoolSize:線程池中允許存在的最大線程數(shù)
keepAliveTime :非核心線程允許的最大閑置時(shí)間,超過(guò)這個(gè)時(shí)間就會(huì)本地銷毀。
workQueue:用來(lái)存放任務(wù)的隊(duì)列。
SynchronousQueue:這個(gè)隊(duì)列會(huì)讓新添加的任務(wù)立即得到執(zhí)行,如果線程池中所有的線程都在執(zhí)行,那么就會(huì)去創(chuàng)建一個(gè)新的線程去執(zhí)行這個(gè)任務(wù)。當(dāng)使用這個(gè)隊(duì)列的時(shí)候,maximumPoolSizes一般都會(huì)設(shè)置一個(gè)最大值 Integer.MAX_VALUE
LinkedBlockingQueue:這個(gè)隊(duì)列是一個(gè)無(wú)界隊(duì)列。怎么理解呢,就是有多少任務(wù)來(lái)我們就會(huì)執(zhí)行多少任務(wù),如果線程池中的線程小于corePoolSize ,我們就會(huì)創(chuàng)建一個(gè)新的線程去執(zhí)行這個(gè)任務(wù),如果線程池中的線程數(shù)等于corePoolSize,就會(huì)將任務(wù)放入隊(duì)列中等待,由于隊(duì)列大小沒(méi)有限制所以也被稱為無(wú)界隊(duì)列。當(dāng)使用這個(gè)隊(duì)列的時(shí)候 maximumPoolSizes 不生效(線程池中線程的數(shù)量不會(huì)超過(guò)corePoolSize),所以一般都會(huì)設(shè)置為0。
ArrayBlockingQueue:這個(gè)隊(duì)列是一個(gè)有界隊(duì)列。可以設(shè)置隊(duì)列的最大容量。當(dāng)線程池中線程數(shù)大于或者等于 maximumPoolSizes 的時(shí)候,就會(huì)把任務(wù)放到這個(gè)隊(duì)列中,當(dāng)當(dāng)前隊(duì)列中的任務(wù)大于隊(duì)列的最大容量就會(huì)丟棄掉該任務(wù)交由 RejectedExecutionHandler 處理。
想要了解更多并發(fā)編程知識(shí)點(diǎn)的,可以關(guān)注我一下,我后續(xù)也會(huì)整理更多相關(guān)技術(shù)點(diǎn)分享出來(lái),另外順便給大家推薦一個(gè)交流學(xué)習(xí)群:650385180,里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源和面試資料,目前受益良多,以下的課程體系圖也是在群里獲取。
最后,本文主要對(duì)Java并發(fā)編程開(kāi)發(fā)需要的知識(shí)點(diǎn)作了簡(jiǎn)單的講解,這里每一個(gè)知識(shí)點(diǎn)都可以用一篇文章去講解,由于篇幅原因不能對(duì)每一個(gè)知識(shí)點(diǎn)都詳細(xì)介紹,我相信通過(guò)本文你會(huì)對(duì)Java的并發(fā)編程會(huì)有更近一步的了解。如果您發(fā)現(xiàn)還有缺漏或者有錯(cuò)誤的地方,可以在評(píng)論區(qū)補(bǔ)充,謝謝。
總結(jié)
以上是生活随笔為你收集整理的干货:Java并发编程必懂知识点解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring Boot 应用的测试
- 下一篇: 云计算逼迫运营商重新出海