1.并發(fā)編程三要素
原子性
原子捐川,即一個不可再被分割的顆粒光酣。在Java中原子性指的是一個或多個操作要么全部執(zhí)行成功要么全部執(zhí)行失敗苔货。
有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行狂男。(處理器可能會對指令進行重排序)
可見性
當(dāng)多個線程訪問同一個變量時埠忘,如果其中一個線程對其作了修改脾拆,其他線程能立即獲取到最新的值。
2. 線程的五大狀態(tài)
創(chuàng)建狀態(tài)
當(dāng)用 new 操作符創(chuàng)建一個線程的時候
就緒狀態(tài)
調(diào)用 start 方法莹妒,處于就緒狀態(tài)的線程并不一定馬上就會執(zhí)行 run 方法名船,還需要等待CPU的調(diào)度
運行狀態(tài)
CPU 開始調(diào)度線程,并開始執(zhí)行 run 方法
阻塞狀態(tài)
線程的執(zhí)行過程中由于一些原因進入阻塞狀態(tài)
比如:調(diào)用 sleep 方法旨怠、嘗試去得到一個鎖等等
死亡狀態(tài)
run 方法執(zhí)行完 或者 執(zhí)行過程中遇到了一個異常
3.悲觀鎖與樂觀鎖
悲觀鎖?:每次操作都會加鎖渠驼,會造成線程阻塞。
樂觀鎖?:每次操作不加鎖而是假設(shè)沒有沖突而去完成某項操作鉴腻,如果因為沖突失敗就重試迷扇,直到成功為止,不會造成線程阻塞爽哎。
4.線程之間的協(xié)作
4.1 wait/notify/notifyAll
這一組是 Object 類的方法
需要注意的是:這三個方法都必須在同步的范圍內(nèi)調(diào)用
wait
阻塞當(dāng)前線程蜓席,直到 notify 或者 notifyAll 來喚醒
wait有三種方式的調(diào)用wait()必要要由 notify 或者 notifyAll 來喚醒wait(longtimeout)在指定時間內(nèi),如果沒有notify或notifAll方法的喚醒课锌,也會自動喚醒厨内。wait(longtimeout,longnanos)本質(zhì)上還是調(diào)用一個參數(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
只能喚醒一個處于 wait 的線程
notifyAll
喚醒全部處于 wait 的線程
4.2 sleep/yield/join
這一組是 Thread 類的方法
sleep
讓當(dāng)前線程暫停指定時間,只是讓出CPU的使用權(quán)渺贤,并不釋放鎖
yield
暫停當(dāng)前線程的執(zhí)行雏胃,也就是當(dāng)前CPU的使用權(quán),讓其他線程有機會執(zhí)行志鞍,不能指定時間瞭亮。會讓當(dāng)前線程從運行狀態(tài)轉(zhuǎn)變?yōu)榫途w狀態(tài),此方法在生產(chǎn)環(huán)境中很少會使用到固棚,官方在其注釋中也有相關(guān)的說明
/*** 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 方法之后(看源碼可知)
使用場景:當(dāng)父線程需要等待子線程執(zhí)行結(jié)束才執(zhí)行后面內(nèi)容或者需要某個子線程的執(zhí)行結(jié)果會用到 join 方法
5.valitate 關(guān)鍵字
5.1 定義
java編程語言允許線程訪問共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新玻孟,線程應(yīng)該確保通過排他鎖單獨獲得這個變量唆缴。Java語言提供了volatile,在某些情況下比鎖更加方便黍翎。如果一個字段被聲明成volatile面徽,java線程內(nèi)存模型確保所有線程看到這個變量的值是一致的。
valitate是輕量級的synchronized,不會引起線程上下文的切換和調(diào)度趟紊,執(zhí)行開銷更小氮双。
5.2 原理
1. 使用volitate修飾的變量在匯編階段,會多出一條lock前綴指令
2. 它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置霎匈,也不會把前面的指令排到內(nèi)存屏障的后面戴差;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成
3. 它會強制將對緩存的修改操作立即寫入主存
4. 如果是寫操作铛嘱,它會導(dǎo)致其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效
5.3 作用
內(nèi)存可見性
多線程操作的時候暖释,一個線程修改了一個變量的值 ,其他線程能立即看到修改后的值
防止重排序
即程序的執(zhí)行順序按照代碼的順序執(zhí)行(處理器為了提高代碼的執(zhí)行效率可能會對代碼進行重排序)
并不能保證操作的原子性(比如下面這段代碼的執(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)鍵字
確保線程互斥的訪問同步代碼
6.1 定義
synchronized 是JVM實現(xiàn)的一種鎖墨吓,其中鎖的獲取和釋放分別是
monitorenter 和 monitorexit 指令球匕,該鎖在實現(xiàn)上分為了偏向鎖、輕量級鎖和重量級鎖帖烘,其中偏向鎖在 java1.6 是默認(rèn)開啟的亮曹,輕量級鎖在多線程競爭的情況下會膨脹成重量級鎖,有關(guān)鎖的數(shù)據(jù)都保存在對象頭中
6.2 原理
加了 synchronized 關(guān)鍵字的代碼段秘症,生成的字節(jié)碼文件會多出 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é)碼文件中會多一個 ACC_SYNCHRONIZED 標(biāo)志位乡摹,當(dāng)方法調(diào)用時役耕,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了趟卸,執(zhí)行線程將先獲取monitor蹄葱,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor锄列。在方法執(zhí)行期間图云,其他任何線程都無法再獲得同一個monitor對象。 其實本質(zhì)上沒有區(qū)別邻邮,只是方法的同步是一種隱式的方式來實現(xiàn)竣况,無需通過字節(jié)碼來完成。
6.3 關(guān)于使用
修飾普通方法
同步對象是實例對象
修飾靜態(tài)方法
同步對象是類本身
修飾代碼塊
可以自己設(shè)置同步對象
6.4 缺點
會讓沒有得到鎖的資源進入Block狀態(tài)筒严,爭奪到資源之后又轉(zhuǎn)為Running狀態(tài)丹泉,這個過程涉及到操作系統(tǒng)用戶模式和內(nèi)核模式的切換,代價比較高鸭蛙。Java1.6為 synchronized 做了優(yōu)化摹恨,增加了從偏向鎖到輕量級鎖再到重量級鎖的過度,但是在最終轉(zhuǎn)變?yōu)橹亓考夋i之后娶视,性能仍然較低晒哄。
7. CAS
AtomicBoolean睁宰,AtomicInteger,AtomicLong以及 Lock 相關(guān)類等底層就是用 CAS實現(xiàn)的寝凌,在一定程度上性能比 synchronized 更高柒傻。想要了解各大互聯(lián)網(wǎng)公司2018最新并發(fā)編程面試題的,可以加群:650385180较木,面試題以及答案在群的共享區(qū)红符。
7.1 什么是CAS
CAS全稱是Compare And Swap,即比較替換伐债,是實現(xiàn)并發(fā)應(yīng)用到的一種技術(shù)预侯。操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)泳赋。 如果內(nèi)存位置的值與預(yù)期原值相匹配雌桑,那么處理器會自動將該位置值更新為新值 。否則祖今,處理器不做任何操作。
7.2 為什么會有CAS
如果只是用 synchronized 來保證同步會存在以下問題
synchronized 是一種悲觀鎖拣技,在使用上會造成一定的性能問題千诬。在多線程競爭下,加鎖膏斤、釋放鎖會導(dǎo)致比較多的上下文切換和調(diào)度延時徐绑,引起性能問題。一個線程持有鎖會導(dǎo)致其它所有需要此鎖的線程掛起莫辨。
7.3 實現(xiàn)原理
Java不能直接的訪問操作系統(tǒng)底層傲茄,是通過native方法(JNI)來訪問。CAS底層通過Unsafe類實現(xiàn)原子性操作沮榜。
7.4 存在的問題
ABA問題
什么是ABA問題盘榨?比如有一個 int 類型的值 N 是 1
此時有三個線程想要去改變它:
線程A :希望給 N 賦值為 2
線程B: 希望給 N 賦值為 2
線程C: 希望給 N 賦值為 1
此時線程A和線程B同時獲取到N的值1,線程A率先得到系統(tǒng)資源蟆融,將 N 賦值為 2草巡,線程 B 由于某種原因被阻塞住,線程C在線程A執(zhí)行完后得到 N 的當(dāng)前值2
此時的線程狀態(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ù)了運行狀態(tài),在阻塞之前線程B獲取到的N的值是1弥喉,執(zhí)行compare操作發(fā)現(xiàn)當(dāng)前N的值與獲取到的值相同(均為1)郁竟,成功將N賦值為了2。
在這個過程中線程B獲取到N的值是一個舊值由境,雖然和當(dāng)前N的值相等棚亩,但是實際上N的值已經(jīng)經(jīng)歷了一次 1到2到1的改變
上面這個例子就是典型的ABA問題
怎樣去解決ABA問題
給變量加一個版本號即可,在比較的時候不僅要比較當(dāng)前變量的值 還需要比較當(dāng)前變量的版本號。Java中AtomicStampedReference 就解決了這個問題
循環(huán)時間長開銷大
在并發(fā)量比較高的情況下蔑舞,如果許多線程反復(fù)嘗試更新某一個變量拒担,卻又一直更新不成功,循環(huán)往復(fù)攻询,會給CPU帶來很大的壓力从撼。
CAS只能保證一個共享變量的原子操作
8. AbstractQueuedSynchronizer(AQS)
AQS抽象的隊列式同步器,是一種基于狀態(tài)(state)的鏈表管理方式钧栖。state 是用CAS去修改的低零。它是 java.util.concurrent 包中最重要的基石,要學(xué)習(xí)想學(xué)習(xí) java.util.concurrent 包里的內(nèi)容這個類是關(guān)鍵拯杠。 ReentrantLock掏婶、CountDownLatcher、Semaphore 實現(xiàn)的原理就是基于AQS潭陪。想知道他怎么實現(xiàn)以及實現(xiàn)原理 可以參看這篇文章 https://www.cnblogs.com/waterystone/p/4920797.html
9. Future
在并發(fā)編程我們一般使用Runable去執(zhí)行異步任務(wù)雄妥,然而這樣做我們是不能拿到異步任務(wù)的返回值的,但是使用Future 就可以依溯。使用Future很簡單老厌,只需把Runable換成FutureTask即可。使用上比較簡單黎炉,這里不多做介紹枝秤。
10. 線程池
如果我們使用線程的時候就去創(chuàng)建一個線程,雖然簡單慷嗜,但是存在很大的問題淀弹。如果并發(fā)的線程數(shù)量很多,并且每個線程都是執(zhí)行一個時間很短的任務(wù)就結(jié)束了庆械,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率薇溃,因為頻繁創(chuàng)建線程和銷毀線程需要時間。線程池通過復(fù)用可以大大減少線程頻繁創(chuàng)建與銷毀帶來的性能上的損耗干奢。
Java中線程池的實現(xiàn)類 ThreadPoolExecutor痊焊,其構(gòu)造函數(shù)的每一個參數(shù)的含義在注釋上已經(jīng)寫得很清楚了,這里幾個關(guān)鍵參數(shù)可以再簡單說一下
corePoolSize :核心線程數(shù)即一直保留在線程池中的線程數(shù)量忿峻,即使處于閑置狀態(tài)也不會被銷毀薄啥。要設(shè)置 allowCoreThreadTimeOut 為 true,才會被銷毀逛尚。
maximumPoolSize:線程池中允許存在的最大線程數(shù)
keepAliveTime :非核心線程允許的最大閑置時間垄惧,超過這個時間就會本地銷毀。
workQueue:用來存放任務(wù)的隊列绰寞。
SynchronousQueue:這個隊列會讓新添加的任務(wù)立即得到執(zhí)行到逊,如果線程池中所有的線程都在執(zhí)行铣口,那么就會去創(chuàng)建一個新的線程去執(zhí)行這個任務(wù)。當(dāng)使用這個隊列的時候觉壶,maximumPoolSizes一般都會設(shè)置一個最大值 Integer.MAX_VALUE
LinkedBlockingQueue:這個隊列是一個無界隊列脑题。怎么理解呢,就是有多少任務(wù)來我們就會執(zhí)行多少任務(wù)铜靶,如果線程池中的線程小于corePoolSize ,我們就會創(chuàng)建一個新的線程去執(zhí)行這個任務(wù)叔遂,如果線程池中的線程數(shù)等于corePoolSize,就會將任務(wù)放入隊列中等待争剿,由于隊列大小沒有限制所以也被稱為無界隊列已艰。當(dāng)使用這個隊列的時候 maximumPoolSizes 不生效(線程池中線程的數(shù)量不會超過corePoolSize),所以一般都會設(shè)置為0蚕苇。
ArrayBlockingQueue:這個隊列是一個有界隊列哩掺。可以設(shè)置隊列的最大容量涩笤。當(dāng)線程池中線程數(shù)大于或者等于 maximumPoolSizes 的時候嚼吞,就會把任務(wù)放到這個隊列中,當(dāng)當(dāng)前隊列中的任務(wù)大于隊列的最大容量就會丟棄掉該任務(wù)交由 RejectedExecutionHandler 處理辆它。
想要了解更多并發(fā)編程知識點的誊薄,可以關(guān)注我一下,我后續(xù)也會整理更多相關(guān)技術(shù)點分享出來锰茉,另外順便給大家推薦一個交流學(xué)習(xí)群:650385180,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring切心,MyBatis飒筑,Netty源碼分析,高并發(fā)绽昏、高性能协屡、分布式、微服務(wù)架構(gòu)的原理全谤,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系肤晓。還能領(lǐng)取免費的學(xué)習(xí)資源和面試資料,目前受益良多认然,以下的課程體系圖也是在群里獲取补憾。
最后,本文主要對Java并發(fā)編程開發(fā)需要的知識點作了簡單的講解卷员,這里每一個知識點都可以用一篇文章去講解盈匾,由于篇幅原因不能對每一個知識點都詳細(xì)介紹,我相信通過本文你會對Java的并發(fā)編程會有更近一步的了解毕骡。如果您發(fā)現(xiàn)還有缺漏或者有錯誤的地方削饵,可以在評論區(qū)補充岩瘦,謝謝。