1. 概述
java中实现多线程最直接、最基础的做法是实现Runnable接口、继承Thread类和实现Callable接口。对于每一个任务创建一个线程的方式,如果任务数量过多,过度消耗资源,会引起内存溢出的问题(Out of Memory)。
实际上,java线程池的引入也来源于生活中的实际例子。我们去火车站买票,如果每来一人购票,车站就开一窗口,那么很快车站的资源就耗尽了,没有场地,没有资金,去一直开下去。假设车站在平时开设5个窗口,我们称其为核心(core)窗口,那么国庆假期或春运期间,购票人数激增,车站会增加窗口,而受资源限制,车站可将窗口最多增至10个,如果超过10人以上来购票,就需要在窗口按照“先来后到(FIFO)”的方式进行排队等候了。
java自JDK 1.5引入了线程池和Executor框架,至此,开发者就可以像“火车站购票”一样,方便的使用线程池对多线程进行管理了。
2. Executor接口
来自JDK1.5的 java.util.concurrent 包。
void execute(Runnable command)
Executor是一个Functional interface,提供了一个接受Runnable命令的方法。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
可以像上面的例子一样使用Executor,但这不是重点,配合线程池才能发挥其作用。
public class ThreadPoolDemo {
private static final int NTHREADS = 100;
private static final Executor es = Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) {
es.execute(() -> System.out.println("execute task"));
}
}
Executors.newFixedThreadPool(NTHREADS)
创建了一个有NTHREADS数量的线程池。当有新的任务提交时,如果线程池有空余的线程,则立即执行,否则,新的任务会放入队列等待。
3. Executor框架提供的线程池
3.1 ExecutorService接口
public interface ExecutorService extends Executor
Executors接口继承了Executor接口,它提供了shutdown()
方法,可以关闭线程池,不再接受新的任务。同时,Executors还增加了submit
方法返回一个Future
对象,来获取异步任务返回的结果。
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
3.2 JDK提供的线程池
Executors类提供了一些工厂方法创建线程池:
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,只有固定数量的线程。在任何时候,线程池中最多有nThreads个激活的线程,如果有额外的任务提交,而没有多余的线程可用时,额外的任务就需要在队列中进行等待。如果有线程因为异常退出,则会补充新的线程。
public static ExecutorService newSingleThreadExecutor()
创建只有一个线程的线程池。
public static ExecutorService newCachedThreadPool()
根据需要创建线程。如果线程池中有可复用的线程,就优先使用可复用的线程,否则添加新的线程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建指定大小的线程池,任务可在指定的延迟时间后执行或周期性的执行。
3.3 自定义线程池
3.2节提到的jdk提供的创建线程池的方法,底层是通过实例化ThreadPoolExecutor
去实现的。newFixedThreadPool
创建固定数量的线程池,当任务过多时,会造成任务的堆积;newCachedThreadPool
根据需要创建,又会造成资源浪费,甚至Out of Memory的问题。一般在项目中更多的是根据需要自定义线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
- 线程池中始终保留的线程个数;
maximumPoolSize
- 允许在线程池中存在的最大线程个数;
keepAliveTime
- 当线程池中线程的数量超过corePoolSize
时,多余的空闲线程的存活时间;
unit
- keepAliveTime
的单位;
workQueue
- 保存已提交但未执行的任务的队列;
threadFactory
- 线程工厂;
handler
- 拒绝策略,当任务过多,来不及执行时的拒绝策略。
备注:ExecutorService 继承了 Executor 接口,ThreadPoolExecutor 实现了 ExecutorService。
4. 一个自定义线程的例子
public class MyThreadPool {
private static ThreadFactory myThreadPoolFactory =
new ThreadFactoryBuilder()
.setNameFormat("MyThread ThreadFactory-pool")
.build();
private static ThreadPoolExecutor EXECUTOR =
new ThreadPoolExecutor(
12,
100,
50L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
myThreadPoolFactory,
new ThreadPoolExecutor.AbortPolicy());
private static Future<Integer> fun(int x) {
return EXECUTOR.submit(() -> x * 2 - 5);
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 8, 9, 10};
List<Future<Integer>> futures = Lists.newArrayList();
Arrays.stream(arr).forEach(x -> futures.add(fun(x)));
try {
List<Integer> list = Lists.newArrayList();
for(Future<Integer> f : futures) {
list.add(f.get());
}
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 超载拒绝策略
线程池提供了4种拒绝策略:
static class ThreadPoolExecutor.AbortPolicy
直接抛出一个RejectedExecutionException
,阻止系统正常工作。
static class ThreadPoolExecutor.CallerRunsPolicy
只要线程池未关闭,该策略直接在调用者线程中运行被丢弃的任务。
static class ThreadPoolExecutor.DiscardOldestPolicy
该策略丢弃最老的任务,也就是丢弃即将被执行的任务,然后重新提交当前任务。这个策略更看重的是当前提交的任务。“只见新人笑,那关旧人哭。”
static class ThreadPoolExecutor.DiscardPolicy
这个策略则是直接把当前无法执行的任务丢弃了。
附录:多线程中类和接口的继承关系(来源于网络)
todo
scheduleExecutorService的例子。