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。