多線程知識(shí)點(diǎn)目錄
多線程并發(fā)(1)- http://www.reibang.com/p/8fcfcac74033
多線程并發(fā)(2)-http://www.reibang.com/p/a0c5095ad103
多線程并發(fā)(3)-http://www.reibang.com/p/c5c3bbd42c35
多線程并發(fā)(4)-http://www.reibang.com/p/e45807a9853e
多線程并發(fā)(5)-http://www.reibang.com/p/5217588d82ba
多線程并發(fā)(6)-http://www.reibang.com/p/d7c888a9c03c
十一太颤、Java阻塞隊(duì)列原理
11.1 線程阻塞的兩種情況:
-
當(dāng)隊(duì)列中沒有數(shù)據(jù)的情況下,消費(fèi)者端的所有線程都會(huì)被自動(dòng)阻塞(掛起)犬金,直到有數(shù)據(jù)放入隊(duì)列护奈。
-
當(dāng)隊(duì)列中填滿數(shù)據(jù)的情況下单鹿,生產(chǎn)者端的所有線程都會(huì)被自動(dòng)阻塞(掛起)园匹,直到隊(duì)列中有空的為止,線程被自動(dòng)喚醒戚丸。
11.2 阻塞隊(duì)列的主要方法
插入操作
add(E paramE):將指定元素插入此隊(duì)列中(如果立即可行且不會(huì)違反容量限制)划址,成功時(shí)返回true,如果當(dāng)前沒有可用空間限府,則拋出IllegalStateException夺颤。如果該元素使NULL,則會(huì)拋出NullPointException異常胁勺。
offer(E paramE):將指定元素插入此隊(duì)列中(如果立即可行且不會(huì)違反容量限制)世澜,成功時(shí)返回true,如果當(dāng)前沒有可用空間署穗,則返回false寥裂。
put(E paramE) throws InterruptedException:將指定元素插入次隊(duì)列中嵌洼,將等待可用的空間(如果有必要),如果隊(duì)列滿了封恰,則線程阻塞等待咱台。
offer(E o, long timeout, TimeUnit unit):可以設(shè)定等待的時(shí)間,如果在指定的時(shí)間內(nèi)俭驮,還不能往隊(duì)列中加入BlockingQueue,則返回失敗春贸。
獲取數(shù)據(jù)操作
poll(time):取走BlockingQueue中排在首位的對(duì)象混萝,若不能立即取出,則可以等time參數(shù)規(guī)定的時(shí)間萍恕,取不到時(shí)返回null逸嘀。
poll(long timeout, TimeUnit unit):從BlockingQueue取出一個(gè)隊(duì)首的對(duì)象,如果在指定時(shí)間內(nèi)允粤,隊(duì)列一單有數(shù)據(jù)可取崭倘,則立即返回隊(duì)列中的數(shù)據(jù)。否則直到時(shí)間超時(shí)還沒數(shù)據(jù)可取类垫,返回失敗司光。
take():取走BlockingQueue里排在首位的對(duì)象,若BlockingQueue為空悉患,阻斷進(jìn)入等待狀態(tài)残家,直到BlockingQueue有新的數(shù)據(jù)被加入。
drainTo():一次性從BlockingQueue獲取所有可用的數(shù)據(jù)對(duì)象(還可以指定獲取數(shù)據(jù)的個(gè)數(shù))售躁,通過該方法坞淮,可以提升獲取數(shù)據(jù)效率;不需要多次分批加鎖或釋放鎖陪捷。
11.3 Java中的阻塞隊(duì)列
- ArrayBlockingQueue:由數(shù)據(jù)結(jié)構(gòu)組成的有界阻塞隊(duì)列回窘。
這是一個(gè)基于數(shù)組的實(shí)現(xiàn)阻塞隊(duì)列。它內(nèi)部使用了一個(gè)數(shù)組來存儲(chǔ)元素市袖,按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序啡直,并且可以設(shè)置最大容量。當(dāng)隊(duì)列滿時(shí)凌盯,如果有線程試圖向隊(duì)列中插入元素付枫,線程將會(huì)被阻塞直到隊(duì)列中有空間;當(dāng)隊(duì)列空時(shí)驰怎,如果有線程試圖從隊(duì)列中刪除元素阐滩,線程將會(huì)被阻塞直到隊(duì)列中有元素。
默認(rèn)情況下不保證訪問者公平的訪問隊(duì)列县忌,所謂公平訪問隊(duì)列是指阻塞的所有生產(chǎn)者線程或消費(fèi)者線程掂榔,當(dāng)隊(duì)列可用時(shí)继效,可以按照阻塞的先后順序訪問隊(duì)列,即先阻塞的生產(chǎn)者線程装获,可以先往隊(duì)列里插入元素瑞信,先阻塞的消費(fèi)者線程,可以先從隊(duì)列里獲取元素穴豫。
- LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列凡简。
這是一個(gè)基于鏈表的實(shí)現(xiàn)阻塞隊(duì)列。它內(nèi)部使用了一個(gè)鏈表來存儲(chǔ)元素精肃,按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序秤涩。與ArrayBlockingQueue不同的是,LinkedBlockingQueue的大小可以動(dòng)態(tài)增長(zhǎng)司抱。當(dāng)隊(duì)列滿時(shí)筐眷,如果有線程試圖向隊(duì)列中插入元素,線程將會(huì)被阻塞直到隊(duì)列中有空間习柠;當(dāng)隊(duì)列空時(shí)匀谣,如果有線程試圖從隊(duì)列中刪除元素,線程將會(huì)被阻塞直到隊(duì)列中有元素资溃。
而 LinkedBlockingQueue 之所以能夠高效的處理并發(fā)數(shù)據(jù)武翎,還因?yàn)槠鋵?duì)于生產(chǎn)者端和消費(fèi)者端分別采用了獨(dú)立的鎖來控制數(shù)據(jù)同步,這也意味著在高并發(fā)的情況下生產(chǎn)者和消費(fèi)者可以并行地操作隊(duì)列中的數(shù)據(jù)肉拓,以此來提高整個(gè)隊(duì)列的并發(fā)性能后频。
- PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。
這是一個(gè)支持優(yōu)先級(jí)排序的阻塞隊(duì)列暖途,默認(rèn)情況下元素采取自然順序升序排列卑惜。它內(nèi)部使用了一個(gè)優(yōu)先級(jí)堆來存儲(chǔ)元素,使得高優(yōu)先級(jí)的元素總是先于低優(yōu)先級(jí)的元素出隊(duì)驻售,需要注意的是不能保證同優(yōu)先級(jí)元素的順序露久。
- DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列。
這是一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的阻塞隊(duì)列欺栗。它內(nèi)部使用了一個(gè)優(yōu)先級(jí)堆來存儲(chǔ)元素毫痕,但與PriorityBlockingQueue不同的是,DelayQueue中的元素只有當(dāng)其指定的延遲時(shí)間到了迟几,才能從隊(duì)列中刪除消请。
- SynchronizedQueue:不存儲(chǔ)元素的阻塞隊(duì)列。
這是一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列类腮。它的所有操作都是對(duì)另一個(gè)阻塞隊(duì)列的操作進(jìn)行同步臊泰,也就是說,它本身并不存儲(chǔ)任何元素蚜枢。每一個(gè) put 操作必須等待一個(gè) take 操作缸逃,否則不能繼續(xù)添加元素针饥。
SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和 ArrayBlockingQueue。
- LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列需频。
這是一個(gè)基于鏈表的無界阻塞隊(duì)列丁眼。它內(nèi)部使用了一個(gè)鏈表來存儲(chǔ)元素。與LinkedBlockingQueue不同的是昭殉,LinkedTransferQueue的大小可以動(dòng)態(tài)增長(zhǎng)且支持多生產(chǎn)者多消費(fèi)者模式苞七。
- LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
這是一個(gè)基于鏈表的雙向阻塞隊(duì)列挪丢。它內(nèi)部使用了一個(gè)鏈表來存儲(chǔ)元素莽鸭,支持在兩端插入和刪除元素。
雙向隊(duì)列指的你可以從隊(duì)列的兩端插入和移出元素吃靠。雙端隊(duì)列因?yàn)槎嗔艘粋€(gè)操作隊(duì)列的入口,在多線程同時(shí)入隊(duì)時(shí)足淆,也就減少了一半的競(jìng)爭(zhēng)巢块。相比其他的阻塞隊(duì)列,LinkedBlockingDeque多了addFirst巧号,addLast族奢,offerFirst,offerLast丹鸿,peekFirst越走,peekLast等方法
十二、volatile關(guān)鍵字的作用
Java語言提供了一種稍弱的同步機(jī)制靠欢,即volatile變量廊敌,用來確保將變量的更新操作通知到其他線程。volatile變量具備兩種特性:①變量可見性门怪;②禁止重排骡澈。volatile變量不會(huì)被緩存在寄存器或者其他處理器不可見的地方,因此在讀取volatile類型的變量是總是會(huì)返回最新寫入的值掷空。
- ① 變量可見性
其一是保證該變量對(duì)所有線程可見肋殴,這里的可見性指的是當(dāng)一個(gè)線程修改了變量的值,那么新的值對(duì)于其他線程時(shí)可以立即獲取的坦弟。 - ② 禁止重排
volatile禁止了指令重排护锤。
比Synchronized更輕量級(jí)的同步鎖
在訪問volatile變量時(shí)不會(huì)執(zhí)行加鎖操作,因此也就不會(huì)執(zhí)行線程阻塞酿傍,因此烙懦,volatile變量是一種比Synchronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。volatile適合這種場(chǎng)景:一個(gè)變量被多個(gè)線程共享拧粪,現(xiàn)成直接給這個(gè)變量賦值修陡。
當(dāng)對(duì)非volatile變量進(jìn)行讀寫的時(shí)候沧侥,每個(gè)線程先從內(nèi)存拷貝變量到CPU緩存中。如果計(jì)算機(jī)有多個(gè)CPU魄鸦,每個(gè)線程可能在不同的CPU上被處理宴杀,這意味著每個(gè)線程可以拷貝到不同的CPU Cache中。而聲明變量是volatile的拾因,JVM保證了每次讀變量都從內(nèi)存中讀旺罢,跳過CPU Cache這一步。
使用場(chǎng)景
volatile變量的單次讀/寫操作可以保證原子性绢记,如long和double類型變量扁达,但是,不能保證i++這種操作的原子性蠢熄,因?yàn)楸举|(zhì)上i++是讀跪解、寫兩次操作。在某些場(chǎng)景下可以代替Synchronized签孔。但是叉讥,volatile不能完全取代Synchronized,只有在一些特殊的場(chǎng)景下才能適用饥追⊥疾郑總的來說,必須同時(shí)滿足下面兩個(gè)條件才能保證在并發(fā)環(huán)境的線程安全:
- 對(duì)變量的寫操作不依賴于當(dāng)前值(i++依賴當(dāng)前值但绕,不能保證線程安全)救崔,或者說是單純的變量賦值(boolean flag=true)
- 該變量沒有包含在具有其他變量的不變式中,也就是說捏顺,不同的volatile變量之間六孵,不能互相依賴。只有在狀態(tài)真正獨(dú)立于程序內(nèi)其他內(nèi)容時(shí)幅骄,才能使用volatile狸臣。
十三、如何在兩個(gè)線程之間共享數(shù)據(jù)
Java里面進(jìn)行多線程通訊的主要方式就是共享內(nèi)存的方式昌执,共享內(nèi)存主要的關(guān)注點(diǎn)有:①可見性烛亦;②有序性;③原子性懂拾。
Java內(nèi)存模型(JMM)解決了可見性和有序性的問題煤禽,而鎖解決了原子性的問題,理想情況下我們希望坐到“同步”和“互斥”岖赋。有以下常規(guī)實(shí)現(xiàn)方法:
13.1 將數(shù)據(jù)抽象成一個(gè)類檬果,并將數(shù)據(jù)的操作作為這個(gè)類的方法
將數(shù)據(jù)抽象成一個(gè)類,并將對(duì)這個(gè)數(shù)據(jù)的操作作為這個(gè)類的方法,這么設(shè)計(jì)可以做到同步选脊,只要在方法上加上“Synchronized”
public class MyThread {
public static void main(String[] args) throws InterruptedException {
MyData myData = new MyData();
Runnable add = new AddRunnable(myData);
Runnable dec = new DecRunnable(myData);
for (int i = 0; i < 2; i++) {
new Thread(add).start();
new Thread(dec).start();
}
}
}
class MyData {
private int j = 0;
public synchronized void add() {
j++;
System.out.println("[add()]線程" + Thread.currentThread().getName() + "的j的值為:" + j);
}
public synchronized void dec() {
j--;
System.out.println("[dec()]線程" + Thread.currentThread().getName() + "的j的值為:" + j);
}
public int getData() {
return j;
}
}
class AddRunnable implements Runnable {
private MyData myData;
public AddRunnable(MyData data) {
this.myData = data;
}
public void run() {
myData.add();
}
}
class DecRunnable implements Runnable {
private MyData myData;
public DecRunnable(MyData data) {
this.myData = data;
}
public void run() {
myData.dec();
}
}
13.2 Runnable對(duì)象作為一個(gè)類的內(nèi)部類
將Runnable對(duì)象作為一個(gè)類的內(nèi)部類杭抠,共享數(shù)據(jù)作為這個(gè)類的成員變量,每個(gè)線程對(duì)共享數(shù)據(jù)的操作方法也封裝在外部類恳啥,以便實(shí)現(xiàn)對(duì)數(shù)據(jù)的各個(gè)操作的同步和互斥偏灿,作為內(nèi)部類的各個(gè)Runnable對(duì)象調(diào)用外部類的這些方法。
public class MyThread {
public static void main(String[] args) throws InterruptedException {
final MyData myData = new MyData();
for (int i = 0; i < 2; i++) {
new Thread(myData::add).start();
new Thread(myData::dec).start();
}
}
}
class MyData {
private int j = 0;
public synchronized void add() {
j++;
System.out.println("[add()]線程" + Thread.currentThread().getName() + "的j的值為:" + j);
}
public synchronized void dec() {
j--;
System.out.println("[dec()]線程" + Thread.currentThread().getName() + "的j的值為:" + j);
}
public int getData() {
return j;
}
}