SynchronousQueue
是一个特殊的阻塞队列,在线程池中有所应用(CachedThreadPool
)。
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
Thread t1=new Thread(() -> {
for (int i=0;i<5;i++){
//非阻塞方法,成功返回true,失败返回false
boolean offer = queue.offer(i);
System.out.println("offer ="+offer);
try {
queue.put(i);
Thread.sleep(300);
} catch (InterruptedException e) {
}
}
},"t1");
Thread t2=new Thread(()->{
for(int i=0;i<10;i++){
//take方法,无值会阻塞
Integer take = null;
try {
take = queue.take();
System.out.println("take ="+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2");
t2.start();
Thread.sleep(3000);
t1.start();
}
今天重点说入队put,和出队take这两个方法。
SynchronousQueue 内部的容量为 0,即它不存储元素。
入队操作时,要遇上其它线程的出队操作,才会成功,反之亦然。
这就特别适合那种,线程之间数据的传递。
比如示例中 t1 线程往队列中放入一个元素,若此时 t2 线程还未执行取元素的操作, t1 就阻塞了。
直到 t2 线程开始执行,从队列中取出该元素,这时 t1被唤醒,两个线程的操作同时成功了。
二、公平与非公平
这里有一个问题,假如有三个线程 t1, t2, t3 先后都往队列放元素,当然都阻塞了。
这时,t4 线程来取一个元素,那之前三个线程,到底哪个被取出呢?
- 公平模式,
先进先出
, t1 线程放的那个元素被取出。 - 非公平模式,
后进先出
,t3 线程放的那个元素被取出。
看到这里,是不是会想起对应的数据结构呢?
是的,队列——先进先出,栈——后进先出。
SynchronousQueue 底层分两种模块,一种是公平的队列模式,一种是非公平的栈模式。
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> synchronousQueue=new SynchronousQueue<>(true);
new Thread(()->{
try {
synchronousQueue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
synchronousQueue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
synchronousQueue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
while (true){
try {
Integer take = synchronousQueue.take();
System.out.println(take);
} catch (InterruptedException e) {
}
}
}).start();
}
- 对于公平模式:输出 1,2,3
- 对于非公平模式:输出3,2,1
三、公平模式
SynchronousQueue<Integer> queue = new SynchronousQueue<>(true);
如上,创建SynchronousQueue 时,传入true,就是公平模式,底层以队列来实现。
初始化时,会new一个空节点, head , tail 都指向这个节点。
假设 t1, t2, t3 先后往队列中放 10, 20, 30。 t1 操作之后,队列变成这样。
t2, t3 放入之后,队列成这样。这三个线程都阻塞
若此时,t4 线程来取元素,此时,head 会移位到 t1 线程那个节点,
且新的head节点 item 设置为 null,老的head节点,next 指向自己,会被GC回收 。
t1 线程被唤醒,执行完后返回。t2, t3线程继续阻塞。
具体源码的解析,下一篇文章细说。
四、非公平模式
假设 t1, t2, t3 先后往队列中放 10, 20, 30。 t1 操作之后,阻塞,如下图
t2, t3 放入之后,队列成这样。这三个线程都阻塞
若此时 t4 线程来取元素,会生成一个新的节点入栈(mode = 2)。
head 和 t3 线程的那个节点会进行匹配,之后出栈。 t 3 线程会被唤醒。t 4 线程取出 30 返回,t 3 线程也会成功返回。
总结说明
需要强调一点的是,前面说 SynchronousQueue 容量为0,不存储元素,只做取与放的交换么?
可是不管是用栈的非公平模式,还是用队列的公平模式,不都是装了很多元素么,这个矛盾吧
其实是这样的,像 LinkedBlockingQueue
、ArrayBlockingQueue
, PriorityBlockingQueue
,在不存在并发的情况下,放入元素时,都能放进去。即便存在并发,拿到锁,只要有位置,就可以放进元素。
但SynchronousQueue
,单纯的放元素,一定是会被阻塞的,即放元素这个操作不能结束。直到有其它线程来取元素时,一放一取的情况下,两者都才能成功,操作一定是成对存在的。