主要用来做两件事
1、利用多核CPU的并发处理能力提高工作效率
2、避免线程的频繁创建和销毁带来的开销来提高工作效率
任务提交到线程池后的处理逻辑
1、提交任务到线程池
2、核心线程池是否已满,没满则创建核心线程数个线程来处理任务
3、如果核心线程数都在工作,新的任务进来先进入到等待队列
4、如果等待队列也满了,还有新的任务进来,看下当前线程数是否达到最大线程数,没有的话赶紧创建线程来执行队列中的任务
5、如果已达最大线程数且等待队列也放满了,还有任务进来,则按照定义好的拒绝策略来处理无法执行的任务
JDK默认提供的线程池
// 单个线程的线程池,多个任务会被缓存到队列中(队列长度Integer.MAX_VALUE)
public static ExecutorService newSingleThreadExecutor();
// 固定数量的线程池,单个线程的升级版,核心和最大线程数一样多,且等待队列是Integer.MAX_VALUE长度的链表
public static ExecutorService newFixedThreadPool(int nThreads);
// 带缓存的线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,类似阻塞队列
public static ExecutorService newCachedThreadPool();
// 定时调度的线程池,返回值与上面3个线程池不同,为ScheduledThreadPoolExecutor,有3中执行方式
// scheduleAtFixedRate 每个调度任务会至少等待指定时间,任务执行的时间超过指定时间,则等待的时间为任务执行的时间
// scheduleWithFixedDelay 第二个任务执行的时间 = 第一个任务执行时间 + delay
// schedule 定时调度,延迟delay后执行,且只执行一次
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 流式(fork-join)线程池,将大任务拆分为很多小任务同时执行,执行完成后汇总结果,类似于MapReduce
public static ExecutorService newWorkStealingPool();
自己创建线程池
阿里的Java开发手册中强制线程池不允许使用Executors去创建,而应该通过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;
}
上面是JDK中最底层创建线程的构造方法,有7参数:
corePoolSize,核心线程数maximumPoolSize,最大线程数keepAliveTime,当线程池数量超过核心线程数时,多余的空闲线程存活的时间unit,时间的单位,可以是毫秒、秒、分钟、小时和天等workQueue,线程池中的线程数超过核心线程数时,任务将放在等待队列,它是一个BlockingQueue类型的对象threadFactory,线程工厂,使用它来创建一个线程handler,拒绝策略,当线程池和等待队列都满了之后的处理逻辑
这里着重看下workQueue、threadFactory和handler
workQueue
BlockingQueue类型的子类都可以作为等待队列,jdk默认提供的阻塞队列有:
1、ArrayBlockingQueue,基于数组实现的有界阻塞队列
2、LinkedBlockingQueue,基于链表实现的阻塞队列,可有界也可无界
3、SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将处于阻塞状态。是Executors.newCachedThreadPool()的默认实现
4、PriorityBlockingQueue,带优先级的无界阻塞队列
threadFactory
线程工厂,ThreadFactory是一个接口,下面是jdk中的定义
public interface ThreadFactory {
Thread newThread(Runnable r);
}
jdk提供了默认实现DefaultThreadFactory,主要用于创建一个线程名字为pool-{poolNum}-thread-{threadNum}的线程,下面是jdk中的实现
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
如果要自定义线程池名字,值需要模仿jdk的实现来自己实现ThreadFactory接口即可
handler
拒绝策略,当线程池满了、队列也满了的时候,我们对任务采取的措施。或者丢弃、或者执行、或者其他...
jdk自带的4中拒绝策略:
CallerRunsPolicy// 在生成线程池的调用者线程执行AbortPolicy// 抛出RejectedExecutionException异常DiscardPolicy// 直接丢弃,不做任何处理DiscardOldestPolicy// 丢弃队列里最旧的那个任务,再尝试执行当前任务
提交任务的两种方法execute()和submit()
主要区别在于:
execute()提交后无返回值,submit()有一个Future对象的返回值
通过调用Future对象的get ()方法,能拿到返回结果。get()会一直阻塞,直到拿到返回结果,当然也可以使用带超时时间的重载方法,在指定时间还没返回直接抛出TimeoutException。
关闭线程池
有两个关闭线程池的方法,shutdown()和shutdownNow()
shutdown()会将线程池状态设置为SHUTDOWN,不在接受新的任务,同时会等待线程池中已有的任务执行完成再结束。
shutdownNow()会将线程池状态设置为SHUTDOWN,对所有线程执行interrupt()操作,情况队列,并将队列中的任务返回回来。
关闭线程池涉及到两个返回boolean的方法,isShutdown()和isTerminated,分别为是否关闭和是否终止
配置正确的线程池参数
属于CPU密集型,那么可以将线程池数量设置成CPU的个数,来减少线程切换带来的开销。属于IO密集型,可以将线程池数量设置得更多一些,比如CPU个数*2。
通过Runtime.getRuntime().availableProcessors()来获取CPU的个数。
线程池监控
ThreadPoolExecutor自带的实现了的方法:
1、getTaskCount(),获取已经执行或正在执行的任务数
2、getCompletedTaskCount(),获取已经执行的任务数
3、getLargestPoolSize(),得到线程池曾经创建过的最大线程数,根据此参数,可以知道线程池是否满过
4、getPoolSize(),获取线程池线程数
5、getActiveCount(),获取活跃线程数,正在执行任务的线程数
ThreadPoolExecutor未实现的方法:
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
我们可以实现这些方法,然后打印出线程执行前,执行后和关闭时的一些信息。