java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
一、并发队列
先看全家福
并发队列又分为阻塞队列与非阻塞队列
- 实现了BlockingQueue的就是阻塞队列,最下层左边5个。队列满的时候放不进去,队列空的时候null都取不出来,会阻塞。
- 最右边2个就是非阻塞队列。
以* Deque结尾的是双端队列,头和尾都能添加和删除。双进双出。一般使用*Queue结尾的。Queue只能一段进一端出。
二、阻塞并发队列
通常,应用于生产者消费者模型。阻塞队列的一段给生产者用,一段给消费者用。阻塞队列是线程安全的,所以生产者消费者都可以是多线程的。
方法
- take():获取并移除队列头结点,如果队列没有数据,则阻塞,直到队列里有数据
- put():插入数据,队列满了的话,则阻塞,直到队列有空闲空间
- add():插入数据,队列满了的话,会抛出异常
- remove():删除数据,队列为空的话,会抛出异常
- element():返回队列头元素,队列为空的话,会抛出异常
- offer():添加一个元素,队列满了的话,会返回false
- poll():取一个元素,队列为空的话,会返回null。取出的同时会删除
- peak():同poll一样,不过取出时不会删除
1、ArrayBlockingQueue
- 有界的
- 初始化需要指定容量
- 公平:指定公平的话,等待最长时间的线程会优先处理
代码实例在我的仓库:https://github.com/MistraR/springboot-advance 包:com.advance.mistra.test.juc.queue
put方法
public void put(E e) throws InterruptedException {
// 判空
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
// 加锁,等待过程中可以被中断
lock.lockInterruptibly();
try {
while (count == items.length)
// 如果队列满了,则阻塞等待。这个nofFull是在初始化时生成的Condition对象
notFull.await();
// 队列没满,则入队
enqueue(e);
} finally {
// 解锁
lock.unlock();
}
}
// ArrayBlockingQueue的部分属性
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
2、LinkedBlockingQueue
- 无界的,最大为Integer.MAX_VALUE
- 结构:Node包装元素,有两把锁,takeLock和putLock
LinkedBlockingQueue
// 部分初始化参数
// 最大容量
private final int capacity;
// 原子类存储当前队列大小
private final AtomicInteger count = new AtomicInteger();
// 有2把锁,put和take互不干扰
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
// 内部类Node
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
put方法
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 使用的put锁
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
// 如果队列满了,则阻塞
notFull.await();
}
// 队列没满,则入队
enqueue(node);
// count+1
c = count.getAndIncrement();
if (c + 1 < capacity)
// 当前容量还没有满,则唤醒一个在等待的put线程
notFull.signal();
} finally {
// 释放put锁
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
3、PriorityBlockingQueue
- 无界的
- 支持优先级,不是先进先出
- 自然排序的,插入的元素必须是可比较的
4、SynchronousQueue
- 容量为0,不存储数据,只做直接交换
- 是Executors.newCachedThreadPool()使用的阻塞队列
5、DelayQueue
- 无界的
- 根据延迟时间排序
- 元素必须实现Delayed接口,规定排序规则
三、非阻塞并发队列
1、ConcurrentLinkedQueue
使用链表的结构,使用CAS非阻塞算法来保证线程安全。跟阻塞队列用ReentrantLock保证并发安全不同。
offer方法
public boolean offer(E e) {
final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
// for死循环
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
// p是尾节点,newNode就是Node保证之后的元素。
// 直接CAS设置尾节点为newNode,设置失败则循环,直到CAS成功
if (NEXT.compareAndSet(p, null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time; failure is OK
TAIL.weakCompareAndSet(this, t, newNode);
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}