SynchronousQueue(同步队列)

SynchronousQueue:同步Queue,属于线程安全的BlockingQueue的一种,此队列设计的理念类似于"单工模式",对于每个put/offer操作,必须等待一个take/poll操作,类似于我们的现实生活中的"火把传递":一个火把传递地他人,需要2个人"触手可及"才行. 因为这种策略,最终导致队列中并没有一个真正的元素;这是一种pipleline思路的基于queue的"操作传递".

  •  void put(E o):向队列提交一个元素,阻塞直到其他线程take或者poll此元素.
  • boolean offer(E o):向队列中提交一个元素,如果此时有其他线程正在被take阻塞(即其他线程已准备接收)或者"碰巧"有poll操作,那么将返回true,否则返回false.
  •   E take():获取并删除一个元素,阻塞直到有其他线程offer/put.
  •  boolean poll():获取并删除一个元素,如果此时有其他线程正在被put阻塞(即其他线程提交元素正等待被接收)或者"碰巧"有offer操作,那么将返回true,否则返回false.
  •  E peek():总会返回null,硬编码.

这个队列中,对我们有意义的操作时put/take,以及put/offer + take或者put/take + poll,对于无法进入队列的元素,需要有额外的"拒绝"策略支持.

SynchronousQueue经常用来,一端或者双端严格遵守"单工"(单工作者)模式的场景,队列的两个操作端分别是productor和consumer.常用于一个productor多个consumer的场景。

在ThreadPoolExecutor中,通过Executors创建的cachedThreadPool就是使用此类型队列.已确保,如果现有线程无法接收任务(offer失败),将会创建新的线程来执行.

下面为简单的例子:

public class SynchronousQueueTest {
	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		SynchronousQueue queue = new SynchronousQueue();
		for(int i=0;i<5;i++){
			Thread t = new SQThread(queue, 1);
		t.start();
		}
		//Thread.sleep(1000);
		for(int i=0;i<10;i++){
			if(!queue.offer(new Object())){
				System.out.println("Failure");
			}
		}
	}
	public static class SQThread extends Thread{
		private SynchronousQueue queue;
		int mode;
		SQThread(SynchronousQueue queue,int mode){
			this.queue = queue;
			this.mode = mode;
		}
		@Override
		public void run(){
			Object item = null;
			try{
				System.out.println(Thread.currentThread().getId());
				if(mode == 1){
					while((item = queue.take()) != null){
					System.out.println(item.toString());
				}
				}else{
				//
				}
			}catch(Exception e){
			//
			}
			System.out.println("end");
		}
	}
} 
   

 

事实上多次执行此程序,会发现不同的结果,同是注释部分Thread.sleep的时间不同,也会对结果不同.所以在线程池中,我们需要"拒绝策略"的支持.

 

SynchronousQueue支持2种策略:公平和非公平,默认为非公平.

1) 公平策略:内部实现为TransferQueue,即一个队列.(consumer和productor将会被队列化)

2) 非公平策略:内部实现为TransferStack,即一个stack(内部模拟了一个单向链表,允许闯入行为)

内部实现比较复杂,尽管支持线程安全,但是其内部并没有使用lock(事实上无法使用lock),使用了LockSupport来控制线程,使用CAS来控制栈的head游标(非公平模式下)。

 

如下为非公平策略下实现

对于consumer而言,其为data消费者,每次take将会检测是否已经有productor的线程在等待,如果有,此时head应该不为null并且head的数据类型为“DATA”(两种类型:DATA 和REQUEST)

此时consumer将消费数据。取消productor的阻塞效果(使用LockSupport.unpark).

如果此时没有productor提供数据,即head为null,此时consumer也将创建一个mode为REQUEST的元素,并阻塞线程。当有productor提供数据时,有productor来取消其阻塞效果。

 

对于productor而言,情况类似。。当put数据时,如果队列中无元素,和take一样,创建一个node为DATA的元素,并阻塞进程。。

如果队列中有元素,则CAS更新head,使head指向新元素,并不断去tryMatch是否有正在阻塞的尚未take到数据的consumer,如果有,就unpark它,并返回数据,并变更列表的next指针。(有点像俄罗斯方块)

如果队列中没有元素(即没有消费者),则自己阻塞。

每个NODE,都有一个waiter属性,标示此在此元素上操作时阻塞的线程(put操作产生的元素未被消费,那么waiter就是put线程;take产生的元素单无元素消费成功时,那么waiter就是take线程)。

 

 

你可能感兴趣的:(JAVA)