阻塞队列
阻塞队列
在线程池中我们可以看到他们使用的场景,以最常用的三种线程池来看,线程池可以查看之前写的文章。
ExecutorService executorService1 = Executors.newFixedThreadPool(10); ExecutorService executorService2 = Executors.newCachedThreadPool(); ExecutorService executorService3 = Executors.newSingleThreadExecutor();
定长:fix linkedBlockingQueue
single:单一 linkedBlockingQueue
cache:变长 synchonousQueue
在框架底层:mq(kafka,rabbitmq,rocketmq),nacos(eruka)等等也都有使用阻塞队列。
队列都实现了接口 BlockingQueue
它提供了一些队列的基本操作,比如:添加操作 add put offer(e,time,unit) offer,取元素操作:remove take poll ,同一个操作多个方法有是时候傻傻分不清有什么不同。
可以参考如下列表
抛异常 阻塞 超时 返回特殊值(boolean,null)
插入 add put offer(e,time,unit) offer
取元素 remove take poll poll
查询元素 element - - peek
ArrayBlockingQueue
1:基于数组实现的阻塞队列,可以实现FIFO,可以作为生产者和消费者
提供了三个构造函数,但是都必须传入一个int数据用来指定队列容量这和LinkedBlockingQueue不一样,后面会看到。
以第二个构造函数为例,第一个构造函数也是调用第二个构造函数,第二个参数默认为false,
/** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and the specified access policy. * * @param capacity the capacity of this queue 队列容量 * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. 如果为true可以让队列在被线程访问的时候保证FIFO,靠new ReentrantLock(fair)公平锁实现
* @throws IllegalArgumentException if {@code capacity < 1} */
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0) throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
/** Condition notEmpty; Condition for waiting takes */
// 上面我们说的 take数据的时候,如果队列为空利用这个条件对象进行阻塞
notEmpty = lock.newCondition();
/** Condition notFull ; Condition for waiting puts*/
// 上面我们说的 put数据的时候,如果队列满了利用这个条件对象进行阻塞
notFull = lock.newCondition();
}
当队列空的时候take阻塞,notEmpty.await()
当有元素入队的时候就会唤醒
当队列满的时候put阻塞,notFull.await()
当有元素出队的时候就会唤醒:notFull.signal()
上面的睡眠和唤醒和await,notify很像,但是他们有很大的区别:
synchronized :自动释放锁 Object 中的方法:wait notify notifyAll(synchronized作用范围内使用) notify是随机唤醒一个线程 ReentrantLock:手动加锁,非公平锁1,公平锁0(所有的线程都有机会拿到锁),响应中断 解锁必须放在finally里面 Condition:await signal signalAll(ReentrantLock作用范围内使用) Condition通过Lock获取的newCondition,获取多次 newCondition返回的对象await 必须用同一个对象signal
从构造器也可以看出,全局只有一把锁 lock,也就说入队和出队用的同一把锁,出队和入队相互阻塞。
还有一点需要留意,在第三种带集合参数的构造器里用了加锁操作,这和指令重排有关系。
我们知道创建一个对象要经过三个过程:1)分配内存 2)初始化数据 3) 内存地址赋值给引用 2,3步骤可能会发生指令重排。
为了防止指令重排,可以加关键字 violatile,加锁锁,对象使用 final 修饰。
而且它的迭代器是线程安全的,我们知道ArrayList不是线程安全,当然它的迭代器也不是线程安全的,但是ArrayBlockingQueue的迭代器是线程安全的,迭代器 是弱一致性 读到的数据不一定准 操作过的元素不会立马体现,
每次创建的迭代器都会注册到Itrs中,这样再迭代过程中某个迭代器对队列的修改,就可以通知其他迭代器知道。
LinkedBlockingQueue
基于节点的数据结构,而且只是单向链表,而且默认的队列长度是Integer.Max_VALUE,可以认为是无界的队列。阻塞,FIFO。
但是它的阻塞和ArrayBlockingQueue不同,ArrayBlockingQueue出队和入队用同一个锁,相互阻塞,LinkedBlockingQueue 出队和入队用不同的锁,互不干扰。
在出队时候有一个需要留意下,也是我们值得学习的地方。
/** * Removes a node from head of queue. * * @return the node */ private E dequeue() { // assert takeLock.isHeldByCurrentThread(); // assert head.item == null; Node<E> h = head; Node<E> first = h.next;
// 有这一步操作,是为了让 h 失去引用,完全独立 让GC快速回收
h.next = h; // help GC head = first; E x = first.item; first.item = null; return x; }
SynchronousQueue
有人称为无缓冲队列,它不存储元素,只用来阻塞线程,在cache线程池中的时候使用这个队列。我们在刚开始的看到cache线程池的构造函数中,是没有核心线程数的,而非核心线程数确实Integer的最大值
它内部没有数组和节点来存储元素的。
我们看构造函数,有一个fair的参数,但是这里的公平非公平不是真正意义上的锁的公平非公平,而是线程的执行的先后,因为这个队列是用来阻塞线程的,我们看到如果为true是TransferQueue,否则是TransferStack。也就是为true的话 线程是FIFO,否则的话就是LIFO.
?
所谓的出队和入队也是靠Transer**来操作的,当一个线程操作put(E)操作的时候,如果队列里没有其它出队线程(比如take)处于阻塞状态,那么这个put操作的线程也会处于阻塞状态,直到有个出队线程执行出队操作,这两个线程就会同时返回。如果只有一个出队线程来操作也会处于阻塞状态,直到有个入队的线程来操作,线程才会返回。