Callable、Future和线程池(ThreadPoolExecutor)的基础学习
本文介绍另外两种创建多线程的方式,这两种方式我们在实际中会用的多一点,尤其是线程池。而在前面文章中我们讲述了创建线程最基本的两种方式:一种是直接继承Thread,另外一种就是实现Runnable接口。但是这两种方式创建线程有一个缺陷,那就是无法获取到线程运行后的结果,因为这两个方式都是重写了 run()方法,而run()方法是用void修饰的。所以后来就有了Callable和Future这两个接口,它们能够获取线程执行的结果。
1、Callable介绍
Callable是在JDK1.5中出现的接口,它和Runnable接口很相似,所以可以认为:Callable接口是Runnable接口的增强版,因为Runnable有的功能Callable都有,而且还能获取任务执行的结果。所以下面来看一下Callable和Runnable接口的对比:
先来看一下Runnable接口的源码:
public interface Runnable {
public abstract void run();
}
Callable接口的源代码:
public interface Callable<V> {
V call() throws Exception;
}
可以很明显的看出它们二者的区别:
- Callable使用的是call(),而Runnable中使用的是run()。
- Callable的call()可以抛出异常,而Runnable的run()不会抛出异常。
- Callable能接受一个泛型,然后在call()中返回一个这个类型的值。而Runnable的run()没有返回值。
- 补充:Callable不能直接替换Runnable,因为Thread类的构造方法根本没有Callable。
上面说Callable是可以返回任务执行结果的,而获取返回结果需使用到Future。所以下面要介绍一下Future。
2、Future介绍
Future也是一个接口,通过它可以获得任务执行的返回值。该接口的内部源码如下:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
可以发现在Future接口中声明了5个方法,下面依次解释每个方法的作用:
- cancel(boolean mayInterruptIfRunning):用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled():表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone():表示任务是否已经完成,若任务完成,则返回true;
- get():用来获取执行结果,这个方法会产生阻塞,阻塞的线程为调用get()方法的线程,会一直等到任务执行完毕返回结果,之后阻塞的主线程才能够往后执行。
- get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就会抛出TimeoutException异常(慎用这个方法,因为有很多坑)。
下面是Future接口中在java.util.concurrent包下类的结构图:
由于Future只是一个接口,所以是无法直接用来创建对象使用的,所以真正获取结果用到的是FutureTask这个类。
『FutureTask』
通过上面的图片发现FutureTask类是实现了RunnableFuture接口,而这个接口又继承了Future接口,我们具体点开其源码来看。
public class FutureTask<V> implements RunnableFuture<V>{
code...
}
打开RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
上面说了这么多,接下来使用Callable+FutureTask创建线程并获取执行结果的一个栗子如下:
- 创建一个实现Callable接口的类。
- 重写call方法,将线程要执行的操作定义在call()中。
- 创建Callable接口实现类的对象。
- 创建FutureTask对象,并将上面Callable接口实现类的对象传入FutureTask构造器中。
- 将FutureTask的对象作为参数传入Thread类的构造器中,创建Thread类对象,并且启动线程。
- 获取Callable中call方法的返回值。
package com.thr;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author Administrator
* @date 2020-04-09
* @desc Callable+Future创建并获取线程执行结果
*/
//1、创建一个实现Callable接口的类
class MyCallable implements Callable<Integer>{
//2、重写call方法,将线程要执行的操作定义在call()中
@Override
public Integer call() throws Exception {
int num=0;
for (int i = 1; i <= 100; i++) {
num+=i;
}
return num;
}
}
public class CallableFutureDemo {
public static void main(String[] args) {
//3、创建Callable接口实现类的对象
MyCallable callable = new MyCallable();
//4、创建FutureTask对象,并将上面Callable接口实现类的对象传入FutureTask构造器中
FutureTask<Integer> task = new FutureTask<Integer>(callable);
//5、将FutureTask的对象作为参数传入Thread类的构造器中,创建Thread类对象,并且启动线程
new Thread(task).start();
try {
//6、获取Callable中call方法的返回值,调用get()时,main线程会阻塞,知道任务线程返回结果
Integer integer = task.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
关于FutureTask的一些小结:
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成。
- 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
- 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。
- 一旦计算完成,就不能再重新开始或取消计算。
- get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
- get()只会计算一次,并且会导致主线程阻塞,所以get()方法一般发在最后。
我们知道Callable用于产生结果,Future用于获取结果。不过Callable和Future一般都和线程池搭配使用,所以下面再来简单介绍一下线程池的使用。
3、线程池的介绍
在前面的文章中介绍了Thread、Runnable和Callable这三种方式创建线程,我们在创建少量线程的时候使用它们是非常的简单方便的,但是如果我们需要创建成百上千的线程时,那么岂不是要创建成百上千个线程对象,调用成百上千的start()方法,可见这样是非常浪费时间、消耗资源和降低程序效率的,因为线程的创建和销毁需要耗费大量的系统资源。那为了解决这一问题出现了线程池。
线程池顾名思义,就是由很多线程构成的池子。在有任务的时候随时取用线程,当任务完成后又将线程放回池中。
所以合理利用线程池能够带来三个好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的创建一般用Executors这个工具类来创建,常见的有以下四种方式:
- newFixedThreadPool(int nThreads):创建一个固定线程数目的线程池,超出的线程处理数量的任务会在队列中等待。
- newSingleThreadExecutor():创建一个单线程化的线程池。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newCacheThreadPool():创建一个可缓存的线程池。如果现有任务没有线程来处理,则创建一个新线程并添加到缓存池中。如果有被使用完但是还没销毁的线程,就复用该线程。如果有线程60s未被使用的话就会从缓存中移出并终止(销毁)。因此,长时间保持空闲的线程池不会使用任何资源。
- newScheduledThreadPool(int corePoolSize):创建一个大小无限的支持定时及周期性的任务执行的线程池,多数情况下可用来替代Time类。
『一般不推荐使用Executors的方式来创建线程池,因为可能会出现OOM(Out Of Memory,内存溢出)的情况,下面我们依次详细的分析这四个方式:』
①、Executors.newFixedThreadPool(int nThread)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
可以发现最后一行使用了LinkedBlockingQueue,泛型是Runnable类型,这里的队列是用来存放线程任务的。我们再来看看这个LinkedBlockingQueue部分源码:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
在上一章博客中提过LinkedBlockingQueue是链表实现的有界阻塞队列,其capacity是可以选择进行设置的,如果不设置的话,将是一个无边界的阻塞队列,队列的最大长度为Integer.MAX_VALUE。而上面newFixedThreadPool的源码中,我们可以很清晰的看到LinkedBlockingQueue是没有指定capacity的。所以此时LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为队列中等待的线程数太多而导致OOM。
下面我们来一个简单的例子,模拟一下使用Executors导致OOM的情况:
首先将JVM参数调一下:-Xmx8m –Xms8m
示例代码如下:
package com.thr;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Administrator
* @date 2020-04-11
* @desc Excutors出现OOM举例
*/
public class ExecutorsDemo {
private static ExecutorService service = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
service.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//do nothing
}
}
}
运行结果:
②、Executors.newSingleThreadExexutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
可以发现还是使用阻塞队列LinkedBlockingQueue,所以问题是一样的。
③、Executors.newCacheThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
可以发现ThreadPoolExecutor对象中的第二个参数为Integer.MAX_VALUE,而这个位置参数的意思为线程池最大线程数。所以还是会出现OOM的情况。
④、Executors.newScheduleThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
code...
}
通过上面三段代码可以发现newScheduleThreadPool()方法返回了ScheduledThreadPoolExecutor对象,而它又继承了ThreadPoolExecutor类,并且调用的是父类的构造器,而构造器中的第二个参数为Integer.MAX_VALUE,所以还是同样的问题。
newScheduleThreadPool()的简单案例(由于前面三个的使用非常的简单,所有就不举例了) 代码如下:
[1]、延迟执行,线程在延迟5秒后执行。
/**
* newScheduleThreadPool()的简单案例
*/
public class ExecutorDemo {
public static void main(String[] args) {
// 创建一个定时处理任务的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
// 设置延迟5秒执行,时间到了就会执行线程
executorService.schedule(() -> {
System.out.println("newScheduledThreadPool---延迟5秒后打印");
}, 5, TimeUnit.SECONDS);
// 释放资源
executorService.shutdown();
}
}
[2]、定期执行,表示延迟5秒后每3秒执行一次。特别注意:线程池不能关闭
/**
* newScheduleThreadPool()的简单案例
*/
public class ExecutorDemo {
public static void main(String[] args) {
// 创建一个定时处理任务的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
// 设置延迟5秒,然后没过3秒打印一次
executorService.scheduleAtFixedRate(() -> {
System.out.println("newScheduledThreadPool---延迟5秒后每过3秒打印一次");
}, 5,3, TimeUnit.SECONDS);
// 注意这里不能释放资源
//executorService.shutdown();
}
}
这就是使用Executors工具类创建线程池的缺陷所在,在《阿里巴巴开发手册》中是不建议使用这种方式创建线程池的,而是推荐使用new ThreadPoolExecutor构造函数来创建线程池。如果你细心一点会发现上面四种方式中其实最终都是使用ThreadPoolExecutor这个类,所以这个类才是线程池的核心,我们只有彻底了解这个类才能真正的理解线程池。
4、ThreadPoolExecutor
上面既然说推荐使用ThreadPoolExecutor来创建线程池,那么先来看一下ThreadPoolExecutor的内容。在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;
}
可以发现构造器有7个参数,这7个参数特别的重要,下面分别解释下构造器中各个参数的含义:
- corePoolSize:核心池的大小。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。所以在默认情况下,创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中,例如LinkedBlockingQueue,如果缓存队列中存放的任务满了,则会继续创建新的线程来执行任务,直到创建maximumPoolSize个线程为止,也就是下面要定义的参数。
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程,所以它的数值肯定是不能小于corePoolSize的,从源码中也可以看到,如果这样做在运行时会抛出异常:IllegalArgumentException。
- keepAliveTime:空闲线程的存活时间。就是当线程的数量大于corePoolSize时,如果等待了keepAliveTime时长还没有任务可执行,则线程终止(前提是线程池中的线程数必须大于corePoolSize时,keepAliveTime才会起作用,否则是没用的),直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
- unit:它为参数keepAliveTime的时间单位,它在TimeUnit类中有7种静态属性可取。
- 天:TimeUnit.DAYS;
- 小时:TimeUnit.HOURS;
- 分钟:TimeUnit.MINUTES;
- 秒:TimeUnit.SECONDS;
- 毫秒:TimeUnit.MILLISECONDS;
- 微妙:TimeUnit.MICROSECONDS;
- 纳秒:TimeUnit.NANOSECONDS;
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序,可以指定缓存队列的大小。
- LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序,吞吐量通常要高于ArrayBlockingQueue,所以这个比较常用,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:它没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素,否则插入操作一直处于阻塞状态。拥有公平(FIFO)和非公平(LIFO,SynchronousQueue默认)两种策略模式,非公平模式很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。但是它的吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定)。但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
- 注:其中ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。
- threadFactory:线程工厂,它用于创建新的线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
- handler:线程饱和策略或拒绝策略。当线程池和队列都满了,再加入的任务会执行此策略,它有四种策略。
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。线程池默认的拒绝策略。如何使用
new ThreadPoolExecutor.AbortPolicy()
。 - DiscardPolicy:也是丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最前面的任务,也就是队列头的元素,然后重新尝试执行任务(重复此过程)。如果此时阻塞队列使用PriorityBlockingQueue优先级队列,将会导致优先级最高的任务被抛弃。
- CallerRunsPolicy:既不抛弃任务也不抛出异常,而是由调用线程的主线程来处理该任务。换言之就是由调用线程池的主线程自己来执行任务(例如:是有main线程启动的线程池,当触发次策略时,多余的任务就会交由main线程来执行),因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成,所以对性能和效率必然是极大的损耗。
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。线程池默认的拒绝策略。如何使用
上面既然介绍完了线程池构造方法中的各个参数,那么再来介绍线程池的工作流程:
- 当线程池中的线程个数小于corePoolSize,每次提交一个新任务到线程池时,都会创建一个新的工作线程来执行任务,直到当前线程数等于corePoolSize;
- 当线程池中的线程个数等于corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
- 当线程池中的线程个数等于corePoolSize时,并且队列也满了(有界队列),这个时候再来新的任务,就会继续创建新的线程去处理(非核心线程),直到线程池中的线程数达到maximumPoolSize。这个时候创建的线程,当线程空闲下来的时候经过keepAliveTime的时间就会被销毁;
- 当线程池中的线程数达到maximumPoolSize时,这个时候再来新的任务,就由饱和策略来处理提交的任务;
注:如果存储任务的队列满了,并且线程数量大于corePoolSize,小于maximumPoolSize,此时创建的线程会优先执行新来的任务,之后在执行队列中的。
我们在打开ThreadPoolExecutor类的代码可以看到,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
public Future<?> submit(Runnable task) {};
public <T> Future<T> submit(Runnable task, T result) { };
public <T> Future<T> submit(Callable<T> task) { };
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException {
};
}
AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。我们接着看ExecutorService接口的实现:
public interface ExecutorService extends Executor {
void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:
public interface Executor {
void execute(Runnable command);
}
所以到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
Executor是一个线程池顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,它就是用来执行传进去的任务的,但没有返回值;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutdown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
- execute(Runnable command):用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
- submit(Callable
task)/submit(Runnable task):用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit),在指定的时间内会等待任务执行,超时则抛出超时异常,等待时候会阻塞当前线程。 - shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
- awaitTermination(long timeout, TimeUnit unit):用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
所以通过上面的讲解大家应该知道创建线程池的正确姿势了吧:
ExecutorService es = new ThreadPoolExecutor(5,,20,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(10));
最后简单的举个创建线程池的例子吧:
package com.thr;
import java.util.concurrent.*;
/**
* @author Administrator
* @date 2020-04-11
* @desc 使用自定义参数ThreadPoolExecutor创建线程池
*/
public class ExecutorServiceDemo {
public static void main(String[] args) {
//定义线程池参数
ExecutorService es = new ThreadPoolExecutor(5, 20,0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10));
//创建Callable和Future对象
MyCallable myCallable = new MyCallable();
Future<Integer> future = es.submit(myCallable);
try {
//获取结果并打印
Integer num = future.get();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
//关闭线程池
es.shutdown();
}
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
当然除了自己定义ThreadPoolExecutor外。还有其他方法。比如各种开源工具如Guava等。这里推荐使用Guava提供的ThreadFactoryBuilder来创建线程池。因为当我们需要给新创建的线程取名字、或者设置为守护线程、错误处理器等操作时,它的好处就体现出来了。简单举例:(注意使用Guava需要引入包)
public class ThreadFactoryBuilderTest {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("线程名称-%s").build();
// 创建一个线程对象
Thread newThread = threadFactory.newThread(()->{
});
System.out.println(newThread.getName());
}
}
参考资料:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构