1.BlockingQueue 线程池阻塞队列
阻塞队列方法
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
}
入队:
- offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false–>不阻塞
- put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断–>阻塞
- offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:–> 阻塞
被唤醒
等待时间超时
当前线程被中断
出队:
- poll():如果没有元素,直接返回null;如果有元素,出队
- take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断–>阻塞
- poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:
被唤醒
等待时间超时
当前线程被中断
(1) ArrayBlockingQueue 先进先出有界循环数组 线程安全
- 利用有限数组存放任务,不支持扩容,每次只允许一个线程获取或者添加
- 利用AQS Lock 来实现阻塞获取,阻塞添加,线程安全,支持公平锁和非公平锁
- 循环数组队列,takeInde putIndex 作为头尾指针,count为现在拥有的数量
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
final Object[] items;//存放元素的数组
int takeIndex;//记录元素被取出元素的数组下标
int putIndex;//记录元素被放入元素的数组下标
int count;//记录元素的个数
//可以选择公平锁或者非公平锁
final ReentrantLock lock;//锁,同样也是保证多线程安全的一个重要因素
//notEmpty是当前lock的阻塞队列,
//作用就是采用内部的一个Condition队列来存储想通过put进行添加元素,但由于数组已满而被阻塞的线程。
private final Condition notEmpty;
//notFull是当前lock的另一个阻塞队列
//作用就是采用内部的Condition队列来存储想通过take进行取出元素,但由于数组为空而被阻塞的线程。
private final Condition notFull;
transient Itrs itrs = null;
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();
}
}
添加方法:
//非阻塞添加方法,没有就立即返回
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
//如果队列满了,阻塞等待别人唤醒。notFull 没有满唤醒。
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
//踢出队列让出位置会唤醒等待的
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
//队列已满,使用超时时间等待, 要么时间到,要么被唤醒,要么被中断。
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
移除方法:
//非阻塞获取,数组没有就立即返回
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
//阻塞获取,数组没有就阻塞等待唤醒
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
//加入队列
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
//队列已满,使用超时时间等待, 要么时间到,要么被唤醒,要么被中断。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
(2) LinkedBlockingQueue 单向有限阻塞队列(两个锁:入队锁和出队锁)
- LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队
- 虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行
- LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的。
- 默认是Integer.MAX_VALUE,有容量危险,而且每次加入都要重新去new,不像ArrayBlockingQueue是提前分配好的。
- 在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private final int capacity;
private final AtomicInteger count = new AtomicInteger();
transient Node<E> head;
private transient Node<E> last;
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
}
阻塞放进队列:(看一下两个不同的锁如何控制入队和出队)
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
//需要新建链表节点对象
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//适用入队锁,putLock,其他流程跟ArrayBlockingQueue 类似。
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
阻塞获取队列:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
//使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
(3) LinkedBlockingDeque 双向链表实现的有界双向并发阻塞队列
- 使用双向链表实现有界阻塞队列
public class LinkedBlockingDeque<E>
extends AbstractQueue<E>
implements BlockingDeque<E>, java.io.Serializable {
//头节点
transient Node<E> first;
//尾巴节点
transient Node<E> last;
//节点数量
private transient int count;
//节点数量边界
private final int capacity;
final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
}
}
获取方法:原理跟ArrayBlockingQueue一样的,不做多分析
public E take() throws InterruptedException {
return takeFirst();
}
public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
while ( (x = unlinkFirst()) == null)
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
Node<E> f = first;
if (f == null)
return null;
Node<E> n = f.next;
E item = f.item;
f.item = null;
f.next = f; // help GC
first = n;
if (n == null)
last = null;
else
n.prev = null;
--count;
//被外界获取走了,腾出空位,让外界放入
notFull.signal();
return item;
}
2.拒绝策略
线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池
//执行任务拒绝的时候,调用拒绝策略的方法
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
//拒绝策略接口
public interface RejectedExecutionHandler {
//拒绝策略
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
(1) CallerRunsPolicy
由提交任务的主线程自己完成,如果完成不了,无法执行后面的任务
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//由调用线程完成处理该任务。这种情况需要让所有任务都执行完毕
if (!e.isShutdown()) {
r.run();
}
}
}
(2) AbortPolicy
丢弃任务并抛出异常。这是默认的拒绝策略
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
//丢弃任务并抛出异常。这是默认的拒绝策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
(3) DiscardPolicy
丢弃任务,但是不抛出异常
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
//丢弃任务,但是不抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
(4) DiscardOldestPolicy
丢弃队列最前面的任务,重新提交给拒绝的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
//丢弃队列最前面的任务,重新提交给拒绝的任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
3. Executors
(1) newCachedThreadPool()线程池 (推荐)
- 核心线程数:0
- 最大线程数:Integer.MAX_VALUE
- 阻塞队列:SynchronousQueue,不会存储元素,相当于没有队列
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(2) newSingleThreadExecutor (阻塞队列长度无限,容易OOM)
- 核心线程数:1
- 最大线程数:1
- 阻塞队列:LinkedBlockingQueue,无限队列
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(3) newFixedThreadPool 创建固定线程数的(最小=最大)
- 核心线程数:n
- 最大线程数:n
- 阻塞队列:LinkedBlockingQueue,无限队列
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(4) newScheduledThreadPool
- 核心线程数:n
- 最大线程数:Integer.MAX_VALUE
- 阻塞队列:DelayedWorkQueue 无界队列,指定多久从队列获取任务
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}