1.阻塞队列——有下面的两个操作:
1)当队列满的时候,队列会阻塞插入元素的线程,直到队列不满。
2)当队列为空时,获取元素的线程会被阻塞,直到队列不为空。
put方法——在队列满了的时候,添加的线程阻塞,无法再添加元素。
take方法——在队列为空的时候,取出的线程阻塞,无法再取出元素。
2.ArrayBlockingQueue:
1)使用数组结构组成的有界阻塞队列
2)默认情况下不保证线程公平的访问队列。非公平性指的是当队列可用时,阻塞的队列都可以争夺队列的资格,有可能先阻塞的线程最后才访问队列。
3.LinkedBlockingQueue:
1)用链表实现的有界阻塞队列,此阻塞队列的默认和最大长度为Integer.MAX_VALUE(整数的最大值)。
4.PriorityBlockingQueue:
1)支持优先级的无界阻塞队列
2)可以在自定义类中使用compareTo()方法来指定元素排序规则,也可以在初始化的时候,在下面的构造函数中指定参数Comparator来对元素进行排序。
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
3)优先队列并不是对队列中的元素排序,而是使用堆来实现,如果按照优先级从小到大就使用小顶堆,如果按照优先级从大到小就使用大顶堆。
示例:
class TestBlockingQueue{
public static void main(String[] args) {
PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
Task t1 = new Task();
Task t2 = new Task();
Task t3 = new Task();
t1.setId(5);
t2.setId(1);
t3.setId(4);
t1.setName("task1");
t2.setName("task2");
t3.setName("task3");
queue.put(t1);
queue.put(t2);
queue.put(t3);
System.out.println(queue); //里面的顺序还是放入的顺序
while(!queue.isEmpty()){ //取出的顺序才是优先级的顺序
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果优先队列已经为空,此时继续取值,会阻塞在这里
try {
queue.take(); //会一直阻塞在这里
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Task implements Comparable<Task>{
int id;
String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public int compareTo(Task task){
return (this.id - task.id);
}
public String toString(){
return id +" "+ name;
}
}
输出:
[1 task2, 5 task1, 4 task3] //看出并未对队列进行排序
1 task2
4 task3
5 task1
优先队列的原理详见博客:
5.SynchronousQueue:
SynchronousQueue是一个不存储元素的阻塞队列,每个put操作必须等待一个take操作,否则不能继续添加元素。(先用take方法阻塞等待,才能有put)。
6.DelayQueue—支持延时获取元素的无界阻塞队列
1)DelayQueue是一个支持延时获取元素的无界阻塞队列。
2)队列使用PriorityQueue来实现,队列中的元素必须实现Dealyed接口。Delayed接口:在创建元素可以指定多久才能从队列中获取当前元素。只有延时期满时才能从队列中获取元素。
示例:一群学生,写作业的时间有长有短,将学生添入队列,加入队列时有学生写作业所需要的时间,在作业完成时,将学生输出。
class Student implements Runnable,Delayed{
String name; //姓名
long costTime;//做试题的时间
long finishedTime;//完成时间
public Student(String name, long costTime) {
this. name = name; //姓名
this. costTime= costTime; //花费的时间
finishedTime = costTime + System. currentTimeMillis(); //完成时间
}
@Override
public void run() { //结束时会输出
System. out.println( name + " 交卷,用时" + costTime /1000);
}
@Override
public long getDelay(TimeUnit unit) { //当前元素还需要延时多长时间,出队列
return ( finishedTime - System. currentTimeMillis());
}
@Override
public int compareTo(Delayed o) { //根据完成的时间排队
Student other = (Student) o;
return finishedTime >= other.finishedTime?1:-1;
//return (int)(finishedTime-other.finishedTime); //第二种写法,本质一样
}
}
public class TestBlockingQueue {
static final int STUDENT_SIZE = 30; //学生数
public static void main(String[] args) throws InterruptedException {
Random r = new Random();
//把所有学生看做一个延迟队列
DelayQueue<Student> students = new DelayQueue<Student>();
//构造一个线程池用来让学生们“做作业”
ExecutorService exec = Executors.newFixedThreadPool(STUDENT_SIZE);
for ( int i = 0; i < STUDENT_SIZE; i++) {
//初始化学生的姓名和做题时间
students.put( new Student( "学生" + (i + 1), 3000 + r.nextInt(10000))); //3s+时间(随机)
}
//开始做题
// while(! students.isEmpty()){
// exec.execute( students.take()); //可以直接多个线程执行输出
// }
while(!students.isEmpty()){ //单个线程输出
Student s = students.take(); //逐个取出
System.out.println(s.name+"花费时间"+s.costTime/1000);;
}
exec.shutdown();
}
}
说明:
1)实现Comparable接口的目的:让延时时间最长的放在队列的末尾。这里的队列是基于PriorityQueue实现的。
通过Comparable让延时最长的放在队列的末尾,然后从队列头开始取出元素,如果元素没有达到延时时间,就阻塞当前线程。这里考虑一种情况,如果我故意将延时最长的放在队头,此时队列头后面的达到延时时间了,但是却出不去,当队列头的延时时间到了后,就会一起出来。例如下面修改了comareTo函数延时最长的放在队头。
public int compareTo(Delayed o) { //根据完成的时间排队
Student other = (Student) o;
return (int)(-finishedTime+other.finishedTime); //第二种写法,本质一样
}
2)队列中的元素必须实现Delayed接口,重写compareTo(Delayed o)方法和getDelay(TimeUnit unit)方法。getDelay函数中不断将finishedTime(完成时间)和现在的时间比较,即计算还需要延时多长时间,当finishedTime=System.currentTimeMillis()时出队列。
public long getDelay(TimeUnit unit) { //当前元素还需要延时多长时间,出队列
return ( finishedTime - System. currentTimeMillis());
}
7.阻塞队列的实现原理:
其实就是等待唤醒模式的应用。
详细原理可以参考Java并发编程艺术6.3.3节,