1. ArrayBlockingQueue使用示例
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列爲空時,獲取元素的線程會等待隊列變爲非空。當隊列滿時,存儲元素的線程會等待隊列可用。阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。
//調用兩個線程來跑
ArrayBlockedQueueTest blockQueue = new ArrayBlockedQueueTest();
blockQueue.startProducerThread();
blockQueue.startConsumerThread();
package com.blockedqueue;
import android.util.Log;
import java.util.concurrent.ArrayBlockingQueue;
public class ArrayBlockedQueueTest {
public static final String TAG = "Test.BlockedQueue";
public static final int SIZE = 2;
public volatile ArrayBlockingQueue<String> blockedQueue = new ArrayBlockingQueue<>(SIZE, true);
private int num = 0;
public ArrayBlockedQueueTest() {
}
//生產者線程
public void startProducerThread() {
Thread producerThread = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
feedData();
}
}
});
producerThread.start();
}
//消費者線程
public void startConsumerThread() {
Thread consumerThread = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
getData();
}
}
});
consumerThread.start();
}
public void feedData() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(blockedQueue.size() >= SIZE){
Log.d(TAG,"feedData size == " + blockedQueue.size());
blockedQueue.remove();
}
String str = "String:" + num;
blockedQueue.add(str);
Log.d(TAG,"feedData: " + str);
num++;
}
public void getData() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
String str = blockedQueue.poll(); //這裏應該使用take()阻塞
if (null == str) {
Log.d(TAG,"getData poll null ..." + blockedQueue.size());
return;
}
Log.d(TAG,"getData: " + str);
}
}
2. 添加和取出方法
擁有的添加和取出方法如下
方法\處理方式 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
檢查方法 | element() | peek() | 不可用 | 不可用 |
- 拋出異常:是指當阻塞隊列滿時候,再往隊列裏插入元素,會拋出 IllegalStateException("Queue full") 異常。當隊列爲空時,從隊列裏獲取元素時會拋出 NoSuchElementException 異常 。
- 返回特殊值:插入方法會返回是否成功,成功則返回 true。移除方法,則是從隊列裏拿出一個元素,如果沒有則返回 null
- 一直阻塞:當阻塞隊列滿時,如果生產者線程往隊列裏 put 元素,隊列會一直阻塞生產者線程,直到拿到數據,或者響應中斷退出。當隊列空時,消費者線程試圖從隊列裏 take 元素,隊列也會阻塞消費者線程,直到隊列可用。
- 超時退出:當阻塞隊列滿時,隊列會阻塞生產者線程一段時間,如果超過一定的時間,生產者線程就會退出。
3. 方法詳細使用
生產者線程慢於消費者
生產者1s入隊一次
消費者0.3s出隊一次
//生產者
public void feedData() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(blockedQueue.size() >= SIZE){
Log.d(TAG,"feedData size == " + blockedQueue.size());
blockedQueue.remove();
}
String str = "String:" + num;
blockedQueue.add(str);
Log.d(TAG,"feedData: " + str);
num++;
}
//消費者
public void getData() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
String str = blockedQueue.poll(); //這裏應該使用take()阻塞
if (null == str) {
Log.d(TAG,"getData poll null ..." + blockedQueue.size());
return;
}
Log.d(TAG,"getData: " + str);
}
結果:消費者會等,poll()出隊是返回null
Test.BlockedQueue: getData poll null ...0
Test.BlockedQueue: getData poll null ...0
Test.BlockedQueue: feedData: String:50
Test.BlockedQueue: getData: String:50
Test.BlockedQueue: getData poll null ...0
Test.BlockedQueue: getData poll null ...0
Test.BlockedQueue: feedData: String:51
Test.BlockedQueue: getData: String:51
使用remove時,隊列爲null,則報NoSuchElementException異常,因此使用這個時應該判斷隊列的size
String str = blockedQueue.remove();
AndroidRuntime: FATAL EXCEPTION: Thread-3
AndroidRuntime: Process: com.tsp.dtest1129, PID: 6581
AndroidRuntime: java.util.NoSuchElementException
AndroidRuntime: at java.util.AbstractQueue.remove(AbstractQueue.java:117)
AndroidRuntime: at com.tsp.blockedqueue.ArrayBlockedQueueTest.getData(ArrayBlockedQueueTest.java:65)
AndroidRuntime: at com.tsp.blockedqueue.ArrayBlockedQueueTest$2.run(ArrayBlockedQueueTest.java:35)
AndroidRuntime: at java.lang.Thread.run(Thread.java:919)
正確寫法:
if(blockedQueue.size() == 0) {
return;
}
String str = blockedQueue.remove();
使用take()時,隊列是null的時候,會阻塞,等隊列不爲null
String str = null;
try {
str = blockedQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"getData: " + str);
Test.BlockedQueue: feedData: String:2
Test.BlockedQueue: getData: String:2
Test.BlockedQueue: feedData: String:3
Test.BlockedQueue: getData: String:3
Test.BlockedQueue: feedData: String:4
Test.BlockedQueue: getData: String:4
Test.BlockedQueue: feedData: String:5
Test.BlockedQueue: getData: String:5
Test.BlockedQueue: feedData: String:6
Test.BlockedQueue: getData: String:6
4. volatile關鍵字
volatile是處理多線程鎖的替代方案,對應有時需要實時的修改共享資源的變量,被volatile修復的變量的值可以立刻被業務方取得最新的值。
不過,猛地感覺,nnd,這不是一樣麼,static是靜態的,所以理論上也可以在不同線程去訪問,能訪問也就是能修改,所以這裏老穆在網上搜了搜 相關的資料,把這個知識點在加強下:
變量放在主存區上,使用該變量的每個線程,都將從主存區拷貝一份到自己的工作區上進行操作。
volatile, 聲明這個字段易變(可能被多個線程使用),Java內存模型負責各個線程的工作區與主存區的該字段的值保持同步,即一致性。
static, 聲明這個字段是靜態的(可能被多個實例共享),在主存區上該類的所有實例的該字段爲同一個變量,即唯一性。
volatile, 聲明變量值的一致性;static,聲明變量的唯一性。
此外,volatile同步機制不同於synchronized, 前者是內存同步,後者不僅包含內存同步(一致性),且保證線程互斥(互斥性)。
static 只是聲明變量在主存上的唯一性,不能保證工作區與主存區變量值的一致性;除非變量的值是不可變的,即再加上final的修飾符,否則static聲明的變量,不是線程安全的。
下面摘自Java語言規範(Java Language Specification)的官方解釋:
1) If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created.
2) A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable。