在互联网的开发场景下,很多业务场景下我们需要使用到多线程的技术,从 Java 5 开始,Java 提供了自己的线程池,线程池就是一个线程的容器,每次只执行额定数量的线程。java.util.concurrent包中提供了ThreadPoolExecutor类来管理线程,本文将介绍一下ThreadPoolExecutor类的使用。
在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:
java.util.concurrent包中提供了多种线程池的创建方式,我们可以直接使用ThreadPoolExecutor类直接创建一个线程池,也可以使用Executors类创建,下面我们分别说一下这几种创建的方式。
Executors类是java.util.concurrent提供的一个创建线程池的工厂类,使用该类可以方便的创建线程池,此类提供的几种方法,支持创建四种类型的线程池,分别是:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。
创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。
/**
* 创建无边界大小的线程池
*/
public static void createCachedThreadPool() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
final int currentIndex = i;
cachedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
上面的Demo中创建了一个无边界限制的线程池,同时使用了一个多线程辅助类CountDownLatch,关于该类的使用,后面会有介绍。
执行结果:
public static void main(String[] args) {
createCachedThreadPool();
}
pool-1-thread-1, currentIndex is : 0
pool-1-thread-5, currentIndex is : 4
pool-1-thread-4, currentIndex is : 3
pool-1-thread-3, currentIndex is : 2
pool-1-thread-2, currentIndex is : 1
pool-1-thread-9, currentIndex is : 8
pool-1-thread-8, currentIndex is : 7
pool-1-thread-7, currentIndex is : 6
pool-1-thread-6, currentIndex is : 5
pool-1-thread-5, currentIndex is : 9
全部线程执行完毕
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。
/**
* 创建固定大小的线程池
*/
public static void createFixedThreadPool() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
public static void main(String[] args) {
createFixedThreadPool();
}
pool-1-thread-4, currentIndex is : 3
pool-1-thread-5, currentIndex is : 4
pool-1-thread-2, currentIndex is : 1
pool-1-thread-1, currentIndex is : 0
pool-1-thread-3, currentIndex is : 2
全部线程执行完毕
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
/**
* 创建给定延迟后运行命令或者定期地执行的线程池
*/
public static void createScheduledThreadPool() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
//定时执行一次的任务,延迟1s后执行
scheduledThreadPool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
}, 1, TimeUnit.SECONDS);
//周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "every 3s"), 2, 3, TimeUnit.SECONDS);
}
}
这里创建了一个调度的线程池,执行两个任务,第一个任务延迟1秒后执行,第二个任务为周期性任务,延迟2秒后,每三秒执行一次
执行结果:
public static void main(String[] args) {
createScheduledThreadPool();
}
pool-1-thread-1, currentIndex is : 0
pool-1-thread-2, currentIndex is : 1
pool-1-thread-3, currentIndex is : 2
pool-1-thread-2, currentIndex is : 3
pool-1-thread-4, currentIndex is : 4
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-3every 3s
pool-1-thread-1every 3s
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-4every 3s
pool-1-thread-4every 3s
pool-1-thread-3every 3s
pool-1-thread-2every 3s
可以看到,第一个任务执行完毕后,开始执行定时调度型任务
该线程池提供了多个方法:
创建一个单线程的线程池,以无界队列方式来运行该线程。当多个任务提交到单线程线程池中,线程池将逐个去进行执行,未执行的任务将放入无界队列进行等待。
/**
* 创建单线程的线程池
*/
public static void createSingleThreadPool() {
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
}
执行结果:
public static void main(String[] args) {
createSingleThreadPool();
}
pool-1-thread-1
线程池方法 | 初始化线程池数 | 最大线程池数 | 线程池中线程存活时间 | 时间单位 | 工作队列 |
---|---|---|---|---|---|
newCachedThreadPool | 0 | Integer.MAX_VALUE | 60 | 秒 | SynchronousQueue |
newFixedThreadPool | 入参指定大小 | 入参指定大小 | 0 | 毫秒 | LinkedBlockingQueue |
newScheduledThreadPool | 入参指定大小 | Integer.MAX_VALUE | 0 | 微秒 | DelayedWorkQueue |
newSingleThreadExecutor | 1 | 1 | 0 | 毫秒 | LinkedBlockingQueue |
Executors类提供4个静态工厂方法:newCachedThreadPool()、newFixedThreadPool(int)、newSingleThreadExecutor和newScheduledThreadPool(int)。这些方法最终都是通过ThreadPoolExecutor类来完成的,当有一些场景需要更细粒度的控制的线程池,可以使用ThreadPoolExecutor方法创建线程池。
/**
* 使用ThreadPoolExecutor创建线程池
*/
public void createThreadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000),
new ThreadPoolExecutor.AbortPolicy());
final CountDownLatch countDownLatch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
final int currentIndex = i;
System.out.println("提交第" + i + "个线程");
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
System.out.println("全部提交完毕");
try {
System.out.println("准备等待线程池任务执行完毕");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
执行结果:
提交第0个线程
提交第1个线程
提交第2个线程
提交第3个线程
提交第4个线程
pool-1-thread-2, currentIndex is : 1
提交第5个线程
pool-1-thread-4, currentIndex is : 3
pool-1-thread-3, currentIndex is : 2
pool-1-thread-1, currentIndex is : 0
pool-1-thread-3, currentIndex is : 5
pool-1-thread-5, currentIndex is : 4
提交第6个线程
提交第7个线程
pool-1-thread-2, currentIndex is : 6
pool-1-thread-2, currentIndex is : 7
全部提交完毕
准备等待线程池任务执行完毕
全部线程执行完毕
接下来看一下ThreadPoolExecutor的中的各个参数的含义。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
BlockingQueue的插入/移除/检查这些方法,对于不能立即满足但可能在将来某一时刻可以满足的操作,共有4种不同的处理方式:第一种是抛出一个异常,第二种是返回一个特殊值(null 或false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。如下表格:
操作 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
调用线程池的shutdown()或shutdownNow()方法来关闭线程池。
中断采用interrupt方法,所以无法响应中断的任务可能永远无法终止。但调用上述的两个关闭之一,isShutdown()方法返回值为true,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()方法。
如何合理地估算线程池大小,这个问题是比较复杂的,比较粗糙的估算方式:
但是根据我在实际应用场景的经验,这种估算有时并不准确,这里不展开讨论线程池大小的设置,可以看一下这一篇文章的分析:如何合理地估算线程池大小
利用线程池提供的参数进行监控,参数如下:
通过扩展线程池进行监控:继承线程池并重写线程池的beforeExecute(),afterExecute()和terminated()方法,可以在任务执行前、后和线程池关闭前自定义行为。如监控任务的平均执行时间,最大执行时间和最小执行时间等。
使用ThreadPoolExecutor直接创建线程池时,可以使用第三方的ThreadFactory,或者自己实现ThreadFactory接口,拓展更多的属性,例如设置线程名称、执行开始时间、优先级等等。
本文介绍了Java 线程池的使用,线程池是非常复杂的一种工具,使用好是一种利器,使用不好也会造成很多问题,所以深入研究线程池还是非常有必要的,希望本文能对您有所帮助。如果您有更好的见解或对本文的内容有指正的地方,请留言给我。
更多Java干货文章请关注我的个人微信公众号:老宣与你聊Java