文章目录
在开发中我们经常会使用到线程,客户端中由于子线程的诸多限制,
向网络请求等耗时操作我们必须得在子线程中执行。通常我们会new Thread
来开启一个线程,这样无限制的创建子线程,他们相互竞争很有可能由于占用
过多资源而导致死机或者OOM。
线程池的优势:
- 降低了系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
- 提高了系统相应速度,当有任务到达时,无需等待新线程的建立便能立即执行;
- 方便线程并发数的管控,线程若无限创建,不仅会额外消耗大量系统资源,更是会占用过多资源而阻塞系统造成OOM等状况。从而降低系统的稳定性。线程池会有效的管控线程,统一分配,调优,提供资源使用率。
- 线程池提供了定时,定期以及可控线程数等功能的线程池。
ThreadPoolExecutor
1. corePoolSize 核心线程数量
线程池中的核心线程数量,默认情况下。核心线程一直存活在线程池中,即便他们在线程池处于闲置状况。
2. maximumPoolSize 线程池最大数量
线程池中所容纳的最大线程量。如果活动线程到了这个数值后,后续任务会被阻塞。(核心线程数+非核心线程数= 线程池总数)
3. keepAliveTime 超时时长
非核心线程闲置的超时时长,对于非核心线程。闲置时间超过这个时间,非核心线程就会被回收。只有对ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候,这个超时时间才会对核心线程产生效果。
4. unit 设置时间单位
用于指定超时时长的时间单位。
5. workQueue 阻塞队列
线程池中保存等待执行的任务的阻塞队列。通过线程**execute()**方法提交的Runnable对象都会存储在该队列中。
阻塞队列 | 说明 |
---|---|
ArrayBlockingQueue | 基于数组实现的有界的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。 |
LinkedBlockingQueue | 基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。 |
SynchronousQueue | 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间。对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。 |
PriorityBlockingQueue | 具有优先级的无限阻塞队列。 |
6. threadFactory 线程工厂
线程工厂,为线程池提供新线程的创建。ThreadFactory是一个接口,里面只有一
个newThread方法。 默认为DefaultThreadFactory类。
7. handler
是RejectedExecutionHandler对象,而RejectedExecutionHandler是一个接口,里
面只有一个rejectedExecution方法。当任务队列已满并且线程池中的活动线程已经
达到所限定的最大值或者是无法成功执行任务,这时候ThreadPoolExecutor会调
用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四个内部类实现了RejectedExecutionHandler接口。在线程池中它默认是AbortPolicy,在无法处理新任务时抛出
RejectedExecutionException异常。
可选值 | 说明 |
---|---|
CallerRunsPolicy | 只用调用者所在线程来运行任务。 |
AbortPolicy | 直接抛出RejectedExecutionException异常。 |
DiscardPolicy | 丢弃掉该任务,不进行处理。 |
DiscardOldestPolicy | 丢弃队列里最近的一个任务,并执行当前任务。 |
8. ArrayBlockingQueue 数组实现阻塞队列
基于数组实现的有界的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。
public class ThreadPoolMain {
public static void main(String[] args) {
ArrayBlockingQueue runnableDemos = new ArrayBlockingQueue<>(2);
ExecutorService arrayBlocking = new ThreadPoolExecutor(1,
2,
5,
TimeUnit.SECONDS, runnableDemos);
((ThreadPoolExecutor) arrayBlocking).allowCoreThreadTimeOut(true);
arrayBlocking.submit(new RunnableDemo("submit-1"));
arrayBlocking.submit(new RunnableDemo("submit-2"));
Future<Integer> submit = arrayBlocking.submit(new Callable<Integer>() {
int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " submit-3" + " " + i);
}
return i;
}
});
arrayBlocking.execute(new RunnableDemo("execute"));
arrayBlocking.execute(new RunnableDemo("execute-2"));
try {
System.out.println("submit-3" + submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static class RunnableDemo implements Runnable {
String code = "";
public RunnableDemo(String code) {
this.code = code;
}
int i = 0;
@Override
public void run() {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " " + code + " " + i);
}
}
}
}
设置:
((ThreadPoolExecutor) arrayBlocking).allowCoreThreadTimeOut(true);
输出结果:
如果线程数超过指定数量则会抛出异常
pool-1-thread-1 submit-1 0
pool-1-thread-1 submit-1 1
pool-1-thread-1 submit-1 2
pool-1-thread-1 submit-2 0
pool-1-thread-1 submit-2 1
pool-1-thread-1 submit-2 2
pool-1-thread-1 submit-3 0
pool-1-thread-1 submit-3 1
pool-1-thread-1 submit-3 2
pool-1-thread-1 execute-2 0
submit-33
pool-1-thread-1 execute-2 1
pool-1-thread-1 execute-2 2
pool-1-thread-2 execute 0
pool-1-thread-2 execute 1
pool-1-thread-2 execute 2
Process finished with exit code 0
2. LinkedBlockingQueue 链表实现的阻塞队列
基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。
private static void linkedBlock() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
2,
4,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
Future<Integer> submit1 = executor.submit(new Callable<Integer>() {
int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " submit-1" + " " + i);
}
return i;
}
});
executor.submit(new RunnableDemo("submit-2"));
Future<Integer> submit = executor.submit(new Callable<Integer>() {
int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " submit-3" + " " + i);
}
return i;
}
});
executor.execute(new RunnableDemo("execute"));
executor.execute(new RunnableDemo("execute-2"));
try {
submit1.get();
System.out.println("submit-3" + submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
结果:
pool-1-thread-1 submit-1 0
pool-1-thread-1 submit-1 1
pool-1-thread-1 submit-1 2
pool-1-thread-1 submit-2 0
pool-1-thread-1 submit-2 1
pool-1-thread-1 submit-2 2
pool-1-thread-1 submit-3 0
pool-1-thread-1 submit-3 1
pool-1-thread-1 submit-3 2
pool-1-thread-1 execute 0
pool-1-thread-1 execute 1
pool-1-thread-1 execute 2
pool-1-thread-1 execute-2 0
pool-1-thread-1 execute-2 1
pool-1-thread-1 execute-2 2
submit-33
SynchronousQueue
内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间。对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。
private static void synchronousQueue() {
SynchronousQueue queue = new SynchronousQueue();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
2,
3,
TimeUnit.SECONDS,
queue);
executor.allowCoreThreadTimeOut(true);
Future<Integer> submit1 = executor.submit(new Callable<Integer>() {
int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " submit-1" + " " + i);
}
return i;
}
});
Future<Integer> submit = executor.submit(new Callable<Integer>() {
int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " submit-3" + " " + i);
}
return i;
}
});
executor.execute(new RunnableDemo("execute-2"));
try {
submit1.get();
System.out.println("submit-3" + submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
结果
若超过数量则抛异常
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ThreadPoolMain$RunnableDemo@45ee12a7 rejected from java.util.concurrent.ThreadPoolExecutor@330bedb4[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at ThreadPoolMain.synchronousQueue(ThreadPoolMain.java:42)
at ThreadPoolMain.main(ThreadPoolMain.java:7)
pool-1-thread-1 submit-1 0
pool-1-thread-1 submit-1 1
pool-1-thread-1 submit-1 2
pool-1-thread-2 submit-3 0
pool-1-thread-2 submit-3 1
pool-1-thread-2 submit-3 2
四种线程池类
Java中四种具有不同功能常见线程池,他们都直接或者间接的配置ThreadPoolExecutor来实现他们的功能。四种线程池分别是newFiexdThreadPool、newCachedThreadPool、newScheduledThreadPool和newSingleThreadExecutor。这四个线程池可以通过Executor类获取。
- newFiexdThreadPool
newFiexdThreadPool使用的是链表实现阻塞队列。
newFiexdThreadPool中不存在超时机制,对任务队列的大小也没有限制。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- newCachedThreadPool
核心线程数为0, 线程池的最大线程数Integer.MAX_VALUE。而Integer.MAX_VALUE是一个很大
的数,也差不多可以说 这个线程池中的最大线程数可以任意大。
当线程池中的线程都处于活动状态的时候,线程池就会创建一个新的线程来处理任务。该线程池中的线程超时时长为60秒,所以当线程处于闲置状态超过60秒的时候便会被回收。
对于newCachedThreadPool他的任务队列采用的是SynchronousQueue,上面说到在SynchronousQueue内部没有任何容量的阻塞队列。SynchronousQueue内部相当于一个空集合,我们无法将一个任务插入到SynchronousQueue中。所以说在线程池中如果现有线程无法接收任务,将会创建新的线程来执行任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
它的核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收
private static void newScheduledThreadPool() {
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.schedule(new RunnableDemo("submit-1"),4, TimeUnit.SECONDS);
service.schedule(new RunnableDemo("submit-5"),4, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new RunnableDemo("submit-2"),1, 3, TimeUnit.SECONDS);
service.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延迟三秒执行");
}
}, 3, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延迟三秒后每隔2秒执行");
}
}, 3, 2, TimeUnit.SECONDS);
}
schedule(Runnable command, long delay, TimeUnit unit) :延迟一定时间后执行Runnable任务;
schedule(Callable callable, long delay, TimeUnit unit) :延迟一定时间后执行Callable任务;
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) :延迟一定时间后,以间隔period时间的频率周期性地执行任务;
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit) :与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
输出结果
pool-1-thread-1 submit-2 0
pool-1-thread-1 submit-2 1
pool-1-thread-1 submit-2 2
pool-1-thread-3延迟三秒执行
pool-1-thread-4延迟三秒后每隔2秒执行
pool-1-thread-2 submit-1 0
pool-1-thread-2 submit-1 1
pool-1-thread-2 submit-1 2
pool-1-thread-1 submit-5 0
pool-1-thread-1 submit-5 1
pool-1-thread-1 submit-5 2
pool-1-thread-3延迟三秒后每隔2秒执行
pool-1-thread-4延迟三秒后每隔2秒执行
pool-1-thread-4延迟三秒后每隔2秒执行
pool-1-thread-1延迟三秒后每隔2秒执行
pool-1-thread-3延迟三秒后每隔2秒执行
pool-1-thread-3延迟三秒后每隔2秒执行
pool-1-thread-4延迟三秒后每隔2秒执行
- newSingleThreadExecutor
在这个线程池中只有一个核心线程,对于任务队列没有大小限制,也就意味着这一个任务处于活动状态时,其他任务都会在任务队列中排队等候依次执行。newSingleThreadExecutor将所有的外界任务统一到一个线程中支持,所以在这个任务执行之间我们不需要处理线程同步的问题
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}