Android ArrayBlockingQueue使用

 

 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。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章