线程池:
为了避免重复的创建线程,线程池的出现可以让线程进行复用。当有任务提交的时候,就会向线程池拿一个线程,当任务完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。这样就可以避免频繁的创建线程而带来的不必要的性能开销.
使用线程池:
在java1.5版本以后,使用线程池建议使用java.util.concurrent包下的已经封装好了的三个静态方法:
- 创建指定线程大小的线程池
Executor executor = Executors.newFixedThreadPool (int nThreads);
- 创建线程数量为1的线程池
Executor executor = Executors.newSingleThreadExecutor();
- 不指定大小,创建可以动态添加线程的线程池
Executor executor = Executors.newCachedThreadPool();
线程池的原理:
其实,上面三个方法实际上都是调用的ThreadPoolExecutor这个类的构造方法,只是参数不一致而已,下面我们看看ThreadPoolExecutor这个类的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
用一个表格说明这个方法需要的参数的意义:
参数名 | 解释 |
---|---|
int corePoolSize | 核心池的大小 |
int maximumPoolSize | 最大的线程池的大小 |
long keepAliveTime | 线程池中超过 corePoolSize的空闲线程最大存活时间 |
TimeUnit unit | keepAliveTime的时间单位,为一个枚举变量 |
BlockingQueue<Runnable> | 阻塞任务的队列 |
ThreadFactory threadFactory | 线程工厂 |
RejectedExecutionHandler handler | 线程池的拒绝策略 |
线程池的工作流程
- 如果当前线程池的线程的数目小于corePoolSize,那么每提交一个任务,就会创建一个线程去执行这个任务;
- 如果线程池的数目大于corePoolSize,那么每提交一个线程,就会尝试将其提交到任务缓存队列,如果添加成功,任务会在线程空闲的时间去执行缓存队列里面的任务;如果添加失败(一般是创建了有界的缓存队列),那么就会另外去创建新的线程去执行这个任务.(这个情形下最大线程数maximumPoolSize 和缓存队列的长度之和要小于你总共需要创建的线程个数).
- 如果最大线程数maximumPoolSize 和缓存队列的长度之和大于你总共需要创建的线程个数,那么就会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止;
用代码演示下此工作流程
package RejectThread;
//需要执行的任务线程
public class SendNoticeTask implements Runnable {
private int count;
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
System.out.println (Thread.currentThread ().getName () + "号工人 开始进行" + count + "号工作任务");
try {
Thread.currentThread ().sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println (Thread.currentThread ().getName ()+"号工人 执行完成");
}
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形一
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> ());
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 3; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
在这里我们设置线程池的核心池大小为3,最大线程数量为10,任务缓存的队列没有设置大小,当你提交的线程数量小于或者等于3的时候,结果基本一样:
如果我们重新设置下提交的任务数量,我们设置需要执行的线程数量为6,缓存队列不设置数量:
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> ());
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 6; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
看下结果:
看的出来,前面三个任务0,1,2号工作任务是直接被执行了的,3,4,5这三个任务被放进了缓存队列,等到前面三个线程有空闲的时候才从缓存队列取出来执行.这个时候不论你提交多少个线程都是不进行拒绝策略的,因为一定可以提交到缓存队列.
下面看下添加失败的情形,我们设置LinkedBlockingQueue的队列大小为1,也就是只能放进去一个线程,在看下执行结果:
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));//队列数量为1
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 6; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
看下执行的结果:
看的出来,0,1,2,4,5号工作任务都是直接创建了线程并执行,而3号工作任务是存入了缓存队列,等待线程有空的时候才进行执行的.
其实,这个流程是,首先0,1,2这三个任务是直接创建线程执行的,因为corePoolSize的大小就是3,然后又来了任务3,4,5,没办法,往缓存队列里面添加,但是缓存队列的大小是1,所以只能加一个进去,也就是3号任务,4,5号任务就直接创建了新的线程来进行执行,等到线程有空闲的时候,从缓存队列里面拿出3号任务继续执行.
情形三在拒绝策略里面讲解.
线程池的拒绝策略
本文章主要演示的是线程池的拒绝策略,一共有4种:
拒绝策略handler | 解释 |
---|---|
AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常 |
DiscardPolicy | 丢弃任务,但是不抛出异常 |
DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) |
CallerRunsPolicy | 由调用线程处理该任务(最常见的是main线程) |
在上述业务中,使用到拒绝策略的情形,我们设置corePoolSize还是3,maximumPoolSize 的大小是10,缓存队列大小还是1,这次我们执行15个任务,这个时候就会使用到拒绝策略了,代码如下:
- 抛异常的拒绝策略AbortPolicy:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
结果如下:
可以看出,0,1,2号任务先直接进行执行,3号任务进入缓存队列,4,5,6,7,8,9,10创建新的线程执行,此时已经达到最大线程创建数10,无法创建,执行拒绝策略,后面的11,12,13,14号任务直接被丢弃并且抛出异常.
- 丢弃任务,不抛异常的策略DiscardPolicy
public DiscardOldestPolicy() { }
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
//工作流程情形二
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardPolicy ());//不抛异常的拒绝策略
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
执行结果,比较简单,不作分析:
- 由调用线程处理该任务(最常见的是main线程)的策略CallerRunsPolicy
为了方便查看效果,我把执行任务的线程结束的输出改了下:
package RejectThread;
public class SendNoticeTask implements Runnable {
private int count;
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
System.out.println (Thread.currentThread ().getName () + "号工人 开始进行" + count + "号工作任务");
try {
Thread.currentThread ().sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println (Thread.currentThread ().getName ()+"号工人 执行完成" + count + "号工作任务~~~~~~");
}
}
使用CallerRunsPolicy的策略:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
注意这里:这里直接调用了需要执行线程的run(),相当于直接进行了方法的调用(大家可以理解下调用run()和start()启动线程的不同就可以理解区别),由于是在主线程里面,所以代表的是执行者运行的策略,CallerRun英文就是这个意思.
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (1));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.CallerRunsPolicy ());
for (int i = 0; i < 15; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
}
}
查看执行结果:
执行结果比较特殊,首先它不抛异常,其次主线程(main)加入了进来,图中上色的是首先弹出的结果,分析结果可以这么理解:这个策略是相当于main线程加入了线程池,也就是现在最大线程容量是11了,3号任务加入了我们定义的缓存队列,也就是当11号任务提交的时候,执行了拒绝策略,将任务分给主线程进行了,由于主线程执行任务也需要时间,所有提交任务的循环就先阻塞在这里了,等到主线程执行完才继续提交下一个任务,此时线程池中的线程已经有部分执行完成了,所以可以继续执行后面的任务.
- 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)的策略DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
package RejectThread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
System.out.println("主线程结开始");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<> (2));
poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardOldestPolicy ());
for (int i = 0; i < 13; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
poolExecutor.execute(task);
}
System.out.println("主线程结束");
运行结果如下:
分析结果可以看出,前面流程是一致的,0,1,2号任务直接创建,3,4号加入缓存队列,5~11新创建线程进行执行,然后你又提交了12号任务,系统的处理流程是你提交一个新的线程,先删除一个缓存队列里面的线程,调用
poolExecutor.execute()方法再执行一次,一直到所有的任务都是最新的,所以丢弃了缓存队列的第一个任务也就是3号任务,这样后续执行了4号和12号任务.
总结
线程池的拒绝策略就是上面四种,如果上面还是不能满足你的要求的话,就需要自定义拒绝策略,做法是写一个类,去实现RejectedExecutionHandler接口,自定义你的代码逻辑:
package RejectThread;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class MyRejectPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
executor.getQueue().poll ();
System.out.println("自定义的拒绝策略");
executor.submit (r);
//r.run ();
}
}
}
进行代码测试:
package RejectThread;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadTest {
public static void main(String[] args) {
System.out.println("主线程结开始");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
0, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<> (2));
poolExecutor.setRejectedExecutionHandler (new MyRejectPolicy());
for (int i = 0; i < 14; i++) {
SendNoticeTask task = new SendNoticeTask();
task.setCount(i);
executor.submit (r);
}
System.out.println("主线程结束");
输出结果:
前面分析应该很简单,主要是当提交第12号任务的时候,它首先打印了我们输出的话,然后使用线程池进行任务的执行,这个时候它就需要等待了.