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节,