多線程知識點目錄
多線程并發(fā)(1)- http://www.itdecent.cn/p/8fcfcac74033
多線程并發(fā)(2)-http://www.itdecent.cn/p/a0c5095ad103
多線程并發(fā)(3)-http://www.itdecent.cn/p/c5c3bbd42c35
多線程并發(fā)(4)-http://www.itdecent.cn/p/e45807a9853e
多線程并發(fā)(5)-http://www.itdecent.cn/p/5217588d82ba
多線程并發(fā)(6)-http://www.itdecent.cn/p/d7c888a9c03c
十一、Java阻塞隊列原理
11.1 線程阻塞的兩種情況:
-
當隊列中沒有數據的情況下,消費者端的所有線程都會被自動阻塞(掛起),直到有數據放入隊列。
情況1 -
當隊列中填滿數據的情況下,生產者端的所有線程都會被自動阻塞(掛起),直到隊列中有空的為止,線程被自動喚醒。
情況2
11.2 阻塞隊列的主要方法

插入操作
add(E paramE):將指定元素插入此隊列中(如果立即可行且不會違反容量限制),成功時返回true,如果當前沒有可用空間,則拋出IllegalStateException。如果該元素使NULL,則會拋出NullPointException異常。
offer(E paramE):將指定元素插入此隊列中(如果立即可行且不會違反容量限制),成功時返回true,如果當前沒有可用空間,則返回false。
put(E paramE) throws InterruptedException:將指定元素插入次隊列中,將等待可用的空間(如果有必要),如果隊列滿了,則線程阻塞等待。
offer(E o, long timeout, TimeUnit unit):可以設定等待的時間,如果在指定的時間內,還不能往隊列中加入BlockingQueue,則返回失敗。
獲取數據操作
poll(time):取走BlockingQueue中排在首位的對象,若不能立即取出,則可以等time參數規(guī)定的時間,取不到時返回null。
poll(long timeout, TimeUnit unit):從BlockingQueue取出一個隊首的對象,如果在指定時間內,隊列一單有數據可取,則立即返回隊列中的數據。否則直到時間超時還沒數據可取,返回失敗。
take():取走BlockingQueue里排在首位的對象,若BlockingQueue為空,阻斷進入等待狀態(tài),直到BlockingQueue有新的數據被加入。
drainTo():一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
11.3 Java中的阻塞隊列
- ArrayBlockingQueue:由數據結構組成的有界阻塞隊列。
這是一個基于數組的實現阻塞隊列。它內部使用了一個數組來存儲元素,按照先進先出(FIFO)的原則對元素進行排序,并且可以設置最大容量。當隊列滿時,如果有線程試圖向隊列中插入元素,線程將會被阻塞直到隊列中有空間;當隊列空時,如果有線程試圖從隊列中刪除元素,線程將會被阻塞直到隊列中有元素。
默認情況下不保證訪問者公平的訪問隊列,所謂公平訪問隊列是指阻塞的所有生產者線程或消費者線程,當隊列可用時,可以按照阻塞的先后順序訪問隊列,即先阻塞的生產者線程,可以先往隊列里插入元素,先阻塞的消費者線程,可以先從隊列里獲取元素。
- LinkedBlockingQueue:由鏈表結構組成的有界阻塞隊列。
這是一個基于鏈表的實現阻塞隊列。它內部使用了一個鏈表來存儲元素,按照先進先出(FIFO)的原則對元素進行排序。與ArrayBlockingQueue不同的是,LinkedBlockingQueue的大小可以動態(tài)增長。當隊列滿時,如果有線程試圖向隊列中插入元素,線程將會被阻塞直到隊列中有空間;當隊列空時,如果有線程試圖從隊列中刪除元素,線程將會被阻塞直到隊列中有元素。
而 LinkedBlockingQueue 之所以能夠高效的處理并發(fā)數據,還因為其對于生產者端和消費者端分別采用了獨立的鎖來控制數據同步,這也意味著在高并發(fā)的情況下生產者和消費者可以并行地操作隊列中的數據,以此來提高整個隊列的并發(fā)性能。
- PriorityBlockingQueue:支持優(yōu)先級排序的無界阻塞隊列。
這是一個支持優(yōu)先級排序的阻塞隊列,默認情況下元素采取自然順序升序排列。它內部使用了一個優(yōu)先級堆來存儲元素,使得高優(yōu)先級的元素總是先于低優(yōu)先級的元素出隊,需要注意的是不能保證同優(yōu)先級元素的順序。
- DelayQueue:使用優(yōu)先級隊列實現的無界阻塞隊列。
這是一個使用優(yōu)先級隊列實現的阻塞隊列。它內部使用了一個優(yōu)先級堆來存儲元素,但與PriorityBlockingQueue不同的是,DelayQueue中的元素只有當其指定的延遲時間到了,才能從隊列中刪除。
- SynchronizedQueue:不存儲元素的阻塞隊列。
這是一個不存儲元素的阻塞隊列。它的所有操作都是對另一個阻塞隊列的操作進行同步,也就是說,它本身并不存儲任何元素。每一個 put 操作必須等待一個 take 操作,否則不能繼續(xù)添加元素。
SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和 ArrayBlockingQueue。
- LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列。
這是一個基于鏈表的無界阻塞隊列。它內部使用了一個鏈表來存儲元素。與LinkedBlockingQueue不同的是,LinkedTransferQueue的大小可以動態(tài)增長且支持多生產者多消費者模式。
- LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列。
這是一個基于鏈表的雙向阻塞隊列。它內部使用了一個鏈表來存儲元素,支持在兩端插入和刪除元素。
雙向隊列指的你可以從隊列的兩端插入和移出元素。雙端隊列因為多了一個操作隊列的入口,在多線程同時入隊時,也就減少了一半的競爭。相比其他的阻塞隊列,LinkedBlockingDeque多了addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast等方法
十二、volatile關鍵字的作用
Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操作通知到其他線程。volatile變量具備兩種特性:①變量可見性;②禁止重排。volatile變量不會被緩存在寄存器或者其他處理器不可見的地方,因此在讀取volatile類型的變量是總是會返回最新寫入的值。
- ① 變量可見性
其一是保證該變量對所有線程可見,這里的可見性指的是當一個線程修改了變量的值,那么新的值對于其他線程時可以立即獲取的。 - ② 禁止重排
volatile禁止了指令重排。
比Synchronized更輕量級的同步鎖
在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會執(zhí)行線程阻塞,因此,volatile變量是一種比Synchronized關鍵字更輕量級的同步機制。volatile適合這種場景:一個變量被多個線程共享,現成直接給這個變量賦值。

當對非volatile變量進行讀寫的時候,每個線程先從內存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以拷貝到不同的CPU Cache中。而聲明變量是volatile的,JVM保證了每次讀變量都從內存中讀,跳過CPU Cache這一步。
使用場景
volatile變量的單次讀/寫操作可以保證原子性,如long和double類型變量,但是,不能保證i++這種操作的原子性,因為本質上i++是讀、寫兩次操作。在某些場景下可以代替Synchronized。但是,volatile不能完全取代Synchronized,只有在一些特殊的場景下才能適用??偟膩碚f,必須同時滿足下面兩個條件才能保證在并發(fā)環(huán)境的線程安全:
- 對變量的寫操作不依賴于當前值(i++依賴當前值,不能保證線程安全),或者說是單純的變量賦值(boolean flag=true)
- 該變量沒有包含在具有其他變量的不變式中,也就是說,不同的volatile變量之間,不能互相依賴。只有在狀態(tài)真正獨立于程序內其他內容時,才能使用volatile。
十三、如何在兩個線程之間共享數據
Java里面進行多線程通訊的主要方式就是共享內存的方式,共享內存主要的關注點有:①可見性;②有序性;③原子性。
Java內存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題,理想情況下我們希望坐到“同步”和“互斥”。有以下常規(guī)實現方法:
13.1 將數據抽象成一個類,并將數據的操作作為這個類的方法
將數據抽象成一個類,并將對這個數據的操作作為這個類的方法,這么設計可以做到同步,只要在方法上加上“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對象作為一個類的內部類
將Runnable對象作為一個類的內部類,共享數據作為這個類的成員變量,每個線程對共享數據的操作方法也封裝在外部類,以便實現對數據的各個操作的同步和互斥,作為內部類的各個Runnable對象調用外部類的這些方法。
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;
}
}

