ThreadPool
基于jdk1.8
1、线程池参数
ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
- corePoolSize: 线程池核心线程数
- maximumPoolSize: 线程池最大数量
- keepAliveTime: 线程空闲时间
- unit: 时间单位
- workQueue: 线程池所使用的缓冲队列
- threadFactory: 线程池创建线程使用的工厂
- handler: 线程池拒绝任务处理策略
2、阻塞队列
BlockingQueue是juc包下一种数据结构,提供线程安全的访问控制。
几种常用阻塞队列
1、ArrayBlockingQueue
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();
}
基于数组的有界阻塞队列。
capacity:初始化队列长度;
fair:队列是否满足FIFO,默认为false
2、DelayQueue
无界有序的BlockingQueue,用于存放实现了Delayed接口的元素。队头元素肯定是最先到期的。底层基于PriorityQueue实现。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
private final PriorityQueue<E> q = new PriorityQueue<E>();
}
实现Delayed接口需要重写 compareTo 和 getDelay 两个方法
new Delayed() {
// 用于在PriorityQueue中排序
@Override
public int compareTo(Delayed o) {
return 0;
}
// 获取延迟时间
@Override
public long getDelay(TimeUnit unit) {
return 0;
}
};
如何实现排序和延迟?
一、排序
往队列中添加元素会调用 PriorityQueue 的 offer方法,DelayQueue 在初始化 PriorityQueue 是没有指定 Comparator,则会走siftUpComparable(int k, E x) 方法,但由于元素实现了 Delayed 接口的 compareTo 方法,所以在添加元素的时候会进行排序。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
二、延迟
取数据是会调用getDelay方法,判断元素是否已到期
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
3、LinkedBlockingQueue
基于链表实现的阻塞队列,初始化是可以指定初始长度,不指定默认长度为 Integer.MAX_VALUE
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}, initially containing the elements of the
* given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
4、PriorityBlockingQueue
无界的优先级阻塞队列,内部基数数组实现,优先级排序与PriorityQueue类似
/**
* Creates a {@code PriorityBlockingQueue} with the specified
* initial capacity that orders its elements according to their
* {@linkplain Comparable natural ordering}.
*
* @param initialCapacity the initial capacity for this priority queue
* @throws IllegalArgumentException if {@code initialCapacity} is less
* than 1
*/
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
/**
* Creates a {@code PriorityBlockingQueue} with the specified initial
* capacity that orders its elements according to the specified
* comparator.
*
* @param initialCapacity the initial capacity for this priority queue
* @param comparator the comparator that will be used to order this
* priority queue. If {@code null}, the {@linkplain Comparable
* natural ordering} of the elements will be used.
* @throws IllegalArgumentException if {@code initialCapacity} is less
* than 1
*/
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
5、SynchronousQueue
同步阻塞队列。
/**
* Creates a {@code SynchronousQueue} with nonfair access policy.
*/
public SynchronousQueue() {
this(false);
}
/**
* Creates a {@code SynchronousQueue} with the specified fairness policy.
*
* @param fair if true, waiting threads contend in FIFO order for
* access; otherwise the order is unspecified.
*/
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
如果调用put往队列中添加元素,则内部同时只能容纳单个元素,如果队列中已有一个元素再往队列中put会被阻塞;同时取数据时如果队列为空也会一直阻塞直到有数据插入。
@Test
public void test1() throws InterruptedException {
// 定义一个SynchronousQueue 默认为非公平队列
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
// 插入数据线程
Thread putThread = new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
System.out.println("++++++++ begin put item: " + i);
queue.put(i);
System.out.println("++++++++ item: " + i + " put end");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 消费数据线程
Thread takeThread = new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
System.out.println("------- begin talk");
Integer result = queue.take();
System.out.println("------- end take result: " + result);
// 每次取完sleep 1s
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
putThread.start();
// 取线程延迟1s开始
Thread.sleep(1000);
takeThread.start();
Thread.sleep(10000);
}
运行结果
++++++++ begin put item: 0
------- begin talk
------- end take result: 0
++++++++ item: 0 put end
++++++++ begin put item: 1
------- begin talk
------- end take result: 1
++++++++ item: 1 put end
++++++++ begin put item: 2
------- begin talk
------- end take result: 2
++++++++ item: 2 put end
注意1: 它一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。
注意2: 它是线程安全的,是阻塞的。
注意3: 不允许使用 null 元素。
注意4: 公平排序策略是指调用put的线程之间,或take的线程之间。公平排序策略可以查考ArrayBlockingQueue中的公平策略。
注意5: SynchronousQueue的以下方法:
- iterator() 永远返回空,因为里面没东西。
- peek() 永远返回null。
- put() 往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。
- offer() 往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false。
- offer(2000, TimeUnit.SECONDS) 往queue里放一个element但是等待指定的时间后才返回,返回的逻辑和offer()方法一样。
- take() 取出并且remove掉queue里的element(认为是在queue里的。。。),取不到东西他会一直等。
- poll() 取出并且remove掉queue里的element(认为是在queue里的。。。),只有到碰巧另外一个线程正在往queue里offer数据或者put数据的时候,该方法才会取到东西。否则立即返回null。
- poll(2000, TimeUnit.SECONDS) 等待指定的时间然后取出并且remove掉queue里的element,其实就是再等其他的thread来往里塞。
- isEmpty()永远是true。
- remainingCapacity() 永远是0。
- remove()和removeAll() 永远是false。
3、拒绝策略
RejectedExecutionHandler 有四种拒绝策略
-
ThreadPoolExecutor.AbortPolicy(中止策略)
功能: 使用时直接抛出RejectedExecutionException
注意: ThreadPoolExecutor默认的拒绝策略就是AbortPolicy,但是如果使用Executors创建的newFixedThreadPool和newSingleThreadExecutor由于底层使用了LinkedBlockingQueue(无界队列),导致一直不会触发此中止策略最后出现oom。
-
ThreadPoolExecutor.DiscardPolicy(丢弃策略)
功能: 直接丢弃这个任务,不做任何处理
使用场景: 如果提交的任务无关紧要可以使用此策略。因为这个策略是个空实现,不会做任何处理所以基本不使用。
-
ThreadPoolExcutor.DiscardOldestPolicy(丢弃老任务策略)
功能: 丢弃队列头部数据,并执行
-
ThreadPoolExecutor.CallerRunsPolicy (调用者执行策略)
功能: 由提交任务的当前线程执行
4、ExcutorService 的四种线程池(不推荐使用)
/*
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
底层分析:corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
使用场景:执行很多短期异步的小程序或者负载较轻的服务器
通俗解释来说就是:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
*/
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
/*
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
底层分析:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
使用场景:执行长期的任务,性能好很多
通俗地来说:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
*/
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
/*
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
底层分析:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
使用场景:一个任务一个任务执行的场景
通俗地来说:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
*/
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
/*
创建一个定长线程池,支持定时和周期性任务执行
底层分析:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
使用场景:周期性执行任务的场景
通俗地来说:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
*/
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
阿里巴巴Java 开发手册中有一条
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
5、推荐创建线程池方式
/**
* 普通线程池
*
* @return ThreadPoolExecutor
*/
@Bean
public ThreadPoolExecutor threadPool() {
int coreThreadNum = Runtime.getRuntime().availableProcessors();
int maxThreadNum = coreThreadNum * 2;
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("ocean_thread").build();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
return new ThreadPoolExecutor(coreThreadNum, maxThreadNum, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100, true), threadFactory, handler);
}
/**
* 延迟线程池
*
* @return ScheduledExecutorService
*/
@Bean
public ScheduledExecutorService scheduledThreadPoolExecutor() {
int coreThreadNum = Runtime.getRuntime().availableProcessors();
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("ocean_scheduled_thread").build();
return new ScheduledThreadPoolExecutor(coreThreadNum, threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
}