一、为什么要有线程池?
1.减少频繁创建和销毁线程的开销
2.增加响应速度,需要调用线程时,不需要等待线程的创建
3.方便对线程的统一管理,避免过多的创建线程导致系统负载
二、线程池的创建方式:
可以调用Executors的3个方法创建线程:
1.创建固定大小的线程池:
ExecutorService threadPool = Executors.newFixedThreadPool(3);
2.创建一个线程的线程池:
ExecutorService threadPool = Executors.newSingleThreadExecutor();
3.创建不限线程数上限的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
上面的三个方法其实都是调用了ThreadPoolExecutor类的构造器来实现的:
例如:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
4.还可以手写一个ThreadPoolExecutor(线程池),注意线程池的7大参数设定
java线程池的完整构造函数:
public ThreadPoolExecutor(int corePoolSize,//线程池长期维持的线程数,即使线程处于idle状态,也不会会回收
int maximumPoolSize,//线程数的上限
long keepAliveTime,//超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收
TimeUnit unit,//
BlockingQueue<Runnable> workQueue,//任务的排队队列
ThreadFactory threadFactory,//新线程的产生方式
RejectedExecutionHandler handler)//拒绝策略
{}
三、线程池的七大参数,4大拒绝策略
七大参数:
public ThreadPoolExecutor(int corePoolSize,//线程池长期维持的线程数,即使线程处于idle状态,也不会会回收
int maximumPoolSize,//线程数的上限
long keepAliveTime,//超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收
TimeUnit unit,//
BlockingQueue<Runnable> workQueue,//任务的排队队列
ThreadFactory threadFactory,//新线程的产生方式
RejectedExecutionHandler handler)//拒绝策略
{}
注意:
1.corePoolSize和maximumPoolSize设置不当,会影响效率,甚至耗尽线程
2.workQueue设置不当会导致OOM
,最好设置有界队列
通过Executors创建线程池采用了默认的任务对列,如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());//无界对列,容易导致OOM
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
3.handler设置不当会导致提交任务的时候抛出异常,最好指定具体的拒绝策略
通过Executors创建线程池采用了默认的拒绝策略,如下:
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
该拒绝策略会抛出RejectedExecutionException异常。
线程池的工作顺序:
corePoolSize->任务队列->maximumPoolSize->拒绝策略
四大拒绝策略:
1.AbortPolicy()【默认】:会抛出RejectedExecutionException异常,该异常为非检测异常,容易忘记捕获,如果不关系任务被拒绝的事情,可以直接设为DiscardPolicy
2.DiscardPolicy():什么也不做,直接丢弃
3.DiscardOldestPolicy():丢弃任务队列中最老的任务,尝试为当前提交的线程腾出位置
4.CallerRunsPolicy():直接由提交任务者执行这个任务,即谁让它过来的谁执行
四、线程池的提交方式:
1.void execute(Runnable command) 没有返回结果
2.Future<?> submit(Runnable task) 有返回结果,future.get()总是为null
3.Future submit(Callable task) 有返回结果
栗子:
/**
* @Author wuxia
* @Date 2020/4/13 9:37
* 测试线程池submit和execute方法区别
*/
public class ExecutorsGetResult {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(5);
FutureTask future = (FutureTask) es.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"\t执行任务");
}
});
try {
//get()方法会阻塞,但是get(2,TimeUnit.SECONDS)会在2s抛出异常TimeoutException
future.get(2, TimeUnit.SECONDS);
System.out.println("es.submit(new Runnable())有返回值,但是是null:->"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("=============================");
FutureTask future1 = (FutureTask) es.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return "2";
}
});
try {
//get()方法会阻塞,但是get(2,TimeUnit.SECONDS)会在2s抛出异常TimeoutException
future.get(2, TimeUnit.SECONDS);
System.out.println("es.submit(new Callable<Object>() )有返回值:->"+future1.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("=============================");
es.execute(new Runnable() {
@Override
public void run() {
System.out.println("execute(new Runnable())只能传Runnable类型");
}
});
}
}
五、获取线程池处理结果和异常:
线程池的处理结果、以及处理过程中的异常都被包装到Future
中,通过在调用Future.get()方法获取该异常,因为submit()本身不会传递结果和任务执行过程中的异常。
栗子:
/**
* 2020-04-11
* 获取线程池处理结果和异常
*/
public class GetResultAndException {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
Future<Object> future = es.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
throw new RuntimeException("该异常会在调用future.get()时传递给调用者~~");
}
});
//ctrl+alt+t
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {//捕获异常
e.printStackTrace();
}
}}
注意
:
获取单个结果:
future.get()会发生阻塞,可以使用 V get(long timeout, TimeUnit unit)指定等待的超时时间。
获取多个结果:
可以依次调用future.get(),但一般多使用ExecutorCompletionService。其中维护了一个阻塞队列,任务执行完成后就会poll到阻塞队列。所以take()去取的时候,阻塞队列中没有完成的任务,就会发生阻塞。take()取任务执行完的结果是无序的,先执行完的任务会先take()到。
源码:
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
测试栗子:
public static void main(String[] args) {
ExecutorService es =Executors.newFixedThreadPool(5);
CompletionService<Object> ecs = new ExecutorCompletionService<Object>(es);// 构造器
for (int i = 0; i < 5; i++) {
ecs.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return Thread.currentThread().getName()+"->"+ UUID.randomUUID().toString().substring(0,8);
}
});
}
for (int i = 0; i < 5; ++i) {// 获取每一个完成的任务
Object r = null;
try {
r = ecs.take().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (r != null)
System.out.println("----"+r);
}
六、使用线程池会带来的问题:
1.使用线程池会带来OOM问题
2.如何配置线程池大小