可以通过ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共有4个构造方法。其中,拥有最多参数的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
corePoolSize
,则创建新线程来处理任务;如果当前运行的线程数等于或多于corePoolSize
,则不再创建新的线程。maximumPoolSize
时,则线程池仍然会创建新的线程来处理任务。allowCoreThreadTimeout
设置为true,核心线程也会被回收。keepAliveTime
参数的时间单位。这是一个枚举,有Days(天)、HOURS(小时)、MINUTES(分钟)、MILLISECONDS(毫秒)、SECONDS(秒)等。corePoolSize
,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,也就是阻塞队列。存储在这里的任务是由ThreadPoolExecutor
的execute
方法提交来的。RejectedExecutionHandler:饱和策略(共4种):
1.AbordPolicy:无法处理新任务,并抛出RejectedExecutionException异常。
2.CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
3.DiscardPolicy:不能执行的任务,并将该任务删除。
4.DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
线程池的原理,当提交一个新的任务到线程池时,线程池的处理流程如下:
ThreadPoolExecutor有两个方法可以供我们执行,分别是submit()和execute(),我们先来看看这两个方法到底有什么差异。
execute()方法源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获得当前线程的生命周期对应的二进制状态码
int c = ctl.get();
//判断当前线程数量是否小于核心线程数量,如果小于就直接创建核心 线程执行任务,创建成功直接跳出,失败则接着往下走.
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//判断线程池是否为RUNNING状态,并且将任务添加至队列中.
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//审核下线程池的状态,如果不是RUNNING状态,直接移除队列中
if (! isRunning(recheck) && remove(command))
reject(command);
//如果当前线程数量为0,则单独创建线程,而不指定任务.
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
else if (!addWorker(command, false))
reject(command);
}
submit()方法源码:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
//还是通过调用execute
execute(ftask);
//最后会将包装好的Runable返回
return ftask;
}
//将Callable 包装进FutureTask中
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
.......
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
所以,通常情况下,在不需要线程执行返回结果值时,我们使用execute 方法。 而当我们需要返回值时,则使用submit方法,他会返回一个Future对象。Future不仅仅可以获得一个结果,他还可以被取消,我们可以通过调用future的cancel()方法,取消一个Future的执行。 比如我们加入了一个线程,但是在这过程中我们又想中断它,则可通过sumbit 来实现。
//创建基本线程池
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(50));
for (int i = 0; i < 30; i++) {
final int finali = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
Log.e("TAG","run : "+finali+" 当前线程:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPoolExecutor.execute(runnable);
}
结果:
每3s打印三次日志。
//源码实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//创建及执行
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//执行上述Demo的runnable
fixedThreadPool.execute(runnable);
结果:每3s打印5次任务,跟上面的基础线程池类似。
特点:参数为核心线程数,只有核心线程,无非核心线程无超时时长,并且阻塞队列无界。
适用:执行长期的任务,性能好很多
//源码实现
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}//创建及执行
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//执行上述Demo的runnable
singleThreadExecutor .execute(runnable);
结果:每3s打印1次任务。
特点:只有一个核心线程,当被占用时,其他的任务需要进入队列等待,fixedThreadPool设置核心线程为1时就是SingleThreadPool。
适用:一个任务一个任务执行的场景
//源码实现
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//创建及执行
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//执行上述Demo的runnable
cachedThreadPool.execute(runnable);
结果:3s后打印30次任务。
特点:没有核心线程,非核心线程无界,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列。
结果分析:
适用:适合执行大量需要立刻处理并且耗时较少的任务
//源码实现
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
//创建及执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//延迟5秒执行
scheduledExecutorService.schedule(runnable, 5, TimeUnit.SECONDS);
//延迟5s后启动,每1s执行一次
scheduledExecutorService.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//启动后第一次延迟5s执行,后面延迟1s执行
scheduledExecutorService.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);
特点:核心线程数量是固定的,非核心线程无界。任务队列采用DelayedWorkQueue,是无界的阻塞队列,所以maximumPoolSize参数无效。ScheduledThreadPool也是四个线程池当中唯一一个具有定时定期执行任务功能的线程池。它适合执行一些周期性任务或者延时任务。
适用:执行一些周期性任务或者延时任务。
一般线程执行完run方法之后,线程就正常结束了,线程池中的任务可以用下面的方式来实现
步骤:
private void cancelAThread() {
ExecutorService pool = Executors.newFixedThreadPool(2);
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("test");
return "true";
}
};
Future<String> f = pool.submit(callable);
System.out.println(f.isCancelled());
System.out.println(f.isDone());
f.cancel(true);
}
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit() 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
5.beforeExecute() - 任务执行前执行的方法
6.afterExecute() -任务执行结束后执行的方法
7.terminated() -线程池关闭后执行的方法
CPU密集型任务:
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
IO密集型任务:
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
混合型任务:
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。