本文大綱
1.并發(fā)編程三要素
-
原子性
原子,即一個(gè)不可再被分割的顆粒项乒。在Java中原子性指的是一個(gè)或多個(gè)操作要么全部執(zhí)行成功要么全部執(zhí)行失敗啰劲。 -
有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。(處理器可能會(huì)對(duì)指令進(jìn)行重排序) -
可見性
當(dāng)多個(gè)線程訪問同一個(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 開始調(diào)度線程,并開始執(zhí)行 run 方法 -
阻塞狀態(tài)
線程的執(zhí)行過程中由于一些原因進(jìn)入阻塞狀態(tài)
比如:調(diào)用 sleep 方法垛孔、嘗試去得到一個(gè)鎖等等?? -
死亡狀態(tài)
run 方法執(zhí)行完 或者 執(zhí)行過程中遇到了一個(gè)異常
3.悲觀鎖與樂觀鎖
- 悲觀鎖:每次操作都會(huì)加鎖藕甩,會(huì)造成線程阻塞。
- 樂觀鎖:每次操作不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作周荐,如果因?yàn)闆_突失敗就重試狭莱,直到成功為止,不會(huì)造成線程阻塞概作。?
4.線程之間的協(xié)作
4.1 wait/notify/notifyAll
這一組是 Object 類的方法
需要注意的是:這三個(gè)方法都必須在同步的范圍內(nèi)調(diào)用?
-
wait
阻塞當(dāng)前線程腋妙,直到 notify 或者 notifyAll 來喚醒????wait有三種方式的調(diào)用 wait() 必要要由 notify 或者 notifyAll 來喚醒???? wait(long timeout) 在指定時(shí)間內(nèi),如果沒有notify或notifAll方法的喚醒讯榕,也會(huì)自動(dòng)喚醒骤素。 wait(long timeout,long nanos) 本質(zhì)上還是調(diào)用一個(gè)參數(shù)的方法 public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } ?
- notify
只能喚醒一個(gè)處于 wait 的線程 - notifyAll
喚醒全部處于 wait 的線程
?
- notify
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)的說明/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@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編程語言允許線程訪問共享變量改淑,為了確保共享變量能被準(zhǔn)確和一致的更新碍岔,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量。Java語言提供了volatile朵夏,在某些情況下比鎖更加方便蔼啦。如果一個(gè)字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的仰猖。
valitate是輕量級(jí)的synchronized捏肢,不會(huì)引起線程上下文的切換和調(diào)度奈籽,執(zhí)行開銷更小。
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ù)無效
5.3 作用
內(nèi)存可見性
多線程操作的時(shí)候,一個(gè)線程修改了一個(gè)變量的值 一睁,其他線程能立即看到修改后的值
防止重排序
即程序的執(zhí)行順序按照代碼的順序執(zhí)行(處理器為了提高代碼的執(zhí)行效率可能會(huì)對(duì)代碼進(jìn)行重排序)
并不能保證操作的原子性(比如下面這段代碼的執(zhí)行結(jié)果一定不是100000)
public class testValitate {
public volatile int inc = 0;
public void increase() {
inc = inc + 1;
}
public static void main(String[] args) {
final testValitate test = new testValitate();
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
for (int j = 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實(shí)現(xiàn)的一種鎖钻弄,其中鎖的獲取和釋放分別是
monitorenter 和 monitorexit 指令,該鎖在實(shí)現(xiàn)上分為了偏向鎖者吁、輕量級(jí)鎖和重量級(jí)鎖窘俺,其中偏向鎖在 java1.6 是默認(rèn)開啟的,輕量級(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 訪問標(biāo)志是否被設(shè)置单鹿,如果設(shè)置了掀宋,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體仲锄,方法執(zhí)行完后再釋放monitor劲妙。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個(gè)monitor對(duì)象儒喊。 其實(shí)本質(zhì)上沒有區(qū)別镣奋,只是方法的同步是一種隱式的方式來實(shí)現(xiàn),無需通過字節(jié)碼來完成怀愧。
6.3 關(guān)于使用
- 修飾普通方法
同步對(duì)象是實(shí)例對(duì)象 - 修飾靜態(tài)方法
同步對(duì)象是類本身 - 修飾代碼塊
可以自己設(shè)置同步對(duì)象?
6.4 缺點(diǎn)
會(huì)讓沒有得到鎖的資源進(jìn)入Block狀態(tài)侨颈,爭(zhēng)奪到資源之后又轉(zhuǎn)為Running狀態(tài),這個(gè)過程涉及到操作系統(tǒng)用戶模式和內(nèi)核模式的切換芯义,代價(jià)比較高哈垢。Java1.6為 synchronized 做了優(yōu)化,增加了從偏向鎖到輕量級(jí)鎖再到重量級(jí)鎖的過度扛拨,但是在最終轉(zhuǎn)變?yōu)橹亓考?jí)鎖之后耘分,性能仍然較低。
7. CAS
AtomicBoolean,AtomicInteger求泰,AtomicLong以及 Lock 相關(guān)類等底層就是用 CAS實(shí)現(xiàn)的央渣,在一定程度上性能比 synchronized 更高。
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 來保證同步會(huì)存在以下問題
synchronized 是一種悲觀鎖惹悄,在使用上會(huì)造成一定的性能問題春叫。在多線程競(jìng)爭(zhēng)下,加鎖泣港、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí)暂殖,引起性能問題。一個(gè)線程持有鎖會(huì)導(dǎo)致其它所有需要此鎖的線程掛起当纱。
7.3 實(shí)現(xiàn)原理
Java不能直接的訪問操作系統(tǒng)底層呛每,是通過native方法(JNI)來訪問。CAS底層通過Unsafe類實(shí)現(xiàn)原子性操作坡氯。
7.4 存在的問題
- ABA問題
什么是ABA問題晨横?比如有一個(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è)過程中線程B獲取到N的值是一個(gè)舊值??罢防,雖然和當(dāng)前N的值相等黍判,但是實(shí)際上N的值已經(jīng)經(jīng)歷了一次 1到2到1的改變
上面這個(gè)例子就是典型的ABA問題?
怎樣去解決ABA問題
給變量加一個(gè)版本號(hào)即可,在比較的時(shí)候不僅要比較當(dāng)前變量的值 還需要比較當(dāng)前變量的版本號(hào)篙梢。Java中AtomicStampedReference 就解決了這個(gè)問題 - 循環(huán)時(shí)間長(zhǎng)開銷大
在并發(fā)量比較高的情況下,如果許多線程反復(fù)嘗試更新某一個(gè)變量美旧,卻又一直更新不成功渤滞,循環(huán)往復(fù)贬墩,會(huì)給CPU帶來很大的壓力。
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)單河咽,但是存在很大的問題。如果并發(fā)的線程數(shù)量很多赋元,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了忘蟹,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間们陆。線程池通過復(fù)用可以大大減少線程頻繁創(chuàng)建與銷毀帶來的性能上的損耗寒瓦。
Java中線程池的實(shí)現(xiàn)類 ThreadPoolExecutor,其構(gòu)造函數(shù)的每一個(gè)參數(shù)的含義在注釋上已經(jīng)寫得很清楚了坪仇,這里幾個(gè)關(guān)鍵參數(shù)可以再簡(jiǎn)單說一下
- corePoolSize :核心線程數(shù)即一直保留在線程池中的線程數(shù)量杂腰,即使處于閑置狀態(tài)也不會(huì)被銷毀。要設(shè)置 allowCoreThreadTimeOut 為 true椅文,才會(huì)被銷毀喂很。
- maximumPoolSize:線程池中允許存在的最大線程數(shù)
- keepAliveTime :非核心線程允許的最大閑置時(shí)間,超過這個(gè)時(shí)間就會(huì)本地銷毀皆刺。
- workQueue:用來存放任務(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è)無界隊(duì)列。怎么理解呢忙干,就是有多少任務(wù)來我們就會(huì)執(zhí)行多少任務(wù)器予,如果線程池中的線程小于corePoolSize ,我們就會(huì)創(chuàng)建一個(gè)新的線程去執(zhí)行這個(gè)任務(wù),如果線程池中的線程數(shù)等于corePoolSize捐迫,就會(huì)將任務(wù)放入隊(duì)列中等待乾翔,由于隊(duì)列大小沒有限制所以也被稱為無界隊(duì)列。當(dāng)使用這個(gè)隊(duì)列的時(shí)候 maximumPoolSizes 不生效(線程池中線程的數(shù)量不會(huì)超過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 處理巧婶。
最后,本文主要對(duì)Java并發(fā)編程開發(fā)需要的知識(shí)點(diǎn)作了簡(jiǎn)單的講解涂乌,這里每一個(gè)知識(shí)點(diǎn)都可以用一篇文章去講解艺栈,由于篇幅原因不能對(duì)每一個(gè)知識(shí)點(diǎn)都詳細(xì)介紹,我相信通過本文你會(huì)對(duì)Java的并發(fā)編程會(huì)有更近一步的了解湾盒。如果您發(fā)現(xiàn)還有缺漏或者有錯(cuò)誤的地方湿右,可以在評(píng)論區(qū)補(bǔ)充,謝謝罚勾。
相關(guān)鏈接
- Java面試毅人,你應(yīng)該準(zhǔn)備這些知識(shí)http://mp.weixin.qq.com/s/0JVy3W9uXwUJZA5_tTtrsw
- Java并發(fā)編程:volatile關(guān)鍵字解析http://www.importnew.com/18126.html
- 深入分析volatile的實(shí)現(xiàn)原理http://blog.csdn.net/lc0817/article/details/51878807
- Java并發(fā)編程:Synchronized及其實(shí)現(xiàn)原理https://www.cnblogs.com/paddix/p/5367116.html
- JVM源碼分析之synchronized實(shí)現(xiàn)http://www.reibang.com/p/c5058b6fe8e5
- Java 并發(fā)編程:線程間的協(xié)作http://www.cnblogs.com/paddix/p/5381958.html
- 漫畫:什么是 CAS 機(jī)制?http://mp.weixin.qq.com/s/f9PYMnpAgS1gAQYPDuCq-w
- Java中Unsafe類詳解https://www.cnblogs.com/mickole/articles/3757278.html
- 深入分析ReentrantLockhttp://blog.csdn.net/jiangjiajian2008/article/details/52226189
版權(quán)聲明:本文為博主原創(chuàng)文章尖殃,未經(jīng)博主允許不得轉(zhuǎn)載丈莺。