Java并发编程4——Executor / ExecutorService / ThreadPoolExecutor线程池

  • 继承关系
    • Executor接口(顶层接口)
    • ExecutorService接口
    • AbstractExecutorService抽象类
    • ThreadPoolExecutor实现类
  • ThreadPoolExecutor
    • 属性
    • 执行方法
    • 线程池状态(5种状态)
    • 构造器(7个参数)
    • 线程池运行机制
    • 阻塞队列workQueue
    • 源码级别
      • Work类
  • Executors工具类提供的线程池实现
    • nexFixedThreadPool:固定大小的线程池
    • newCachedThreadPool:带缓冲池的线程池
    • newSingleThreadExecutor:单线程线程池
  • 其他
    • submit() / invokeAny() / invokeAll()使用示例

4.1 继承关系

  • Executor接口:顶层接口,只定义了一个execute(Runnable)方法
void execute(Runnable command)
  • 任务执行方法
  • 可能会抛出异常
    • RejectedExecutionException:任务被线程池拒绝
    • NullPointerException:Runnable对象为null
  • ExecutorService接口:继承了Executor接口,定义了两种方法:生命周期管理的相关方法,任务提交的方法:submit(),invokeAll() / invokeAny()

生命周期管理的相关方法

(只定义接口方法,实现方法全部由ThreadPoolExecutor实现)

  • void shutdown();

  1. 把线程池状态设置成SHUTDOWN
  2.  正在执行的线程继续执行
  3.  队列中的也会执行完,但不再接受新任务
  • List<Runnable> shutdownNow();
  1. 把线程池状态设置成STOP
  2. 正在执行的任务立刻终止
  3. 没有执行的任务队列返回
  • boolean awaitTermination(long timeout, TimeUnit unit)
  • 代码运行到这个方法会被阻塞,两种情况会放行
    • 线程池状态为TERMINATED(比如执行了shutdown方法之后)
    • 阻塞时间超过timeout之后,会被放行
  • boolean isTerminated();
  • boolean isShutdown();
  • 线程池状态是否是TERMINATED
  • 线程池状态是否》=SHUTDOWN(SHUTDOWN, STOP, TIDYING, TERMINATED),换句话说:只要不处于RUNNING的线程,都返回true

submit()方法

(只定义接口方法,实现方法全部由AbstractExecutorService实现)

  • Future<?> submit(Runnable task) //无返回值,返回值为null
  • <T> Future<T> submit(Runnable task, T result) //通过传入的result间接获得返回值
  • <T> Future<T> submit(Callable<T> task) //有返回值
  • 提交任务,用返回值Future获得任务结果
  • 三者的源码都是把任务封装成FutureTask对象,扔给execute()方法执行

invokeAll() / invokeAny()

(只定义接口方法,实现方法全部由AbstractExecutorService实现)

  • List<Future<T>> invokeAll(Collection<继承Callable<T>的类> tasks);
  • List<Future<T>> invokeAll(Collection<继承Callable<T>的类> tasks, long timeout, TimeUnit unit);
提交tasks中所有任务
  • T invokeAny(Collection<继承Callable<T>的类> tasks);
  • T invokeAny(Collection<继承Callable<T>的类> tasks, long timeout, TimeUnit unit);
提交tasks中所有任务,哪个先执行完哪个先返回结果,其他任务取消
  • AbstractExecutorService抽象类:继承了ExecutorService接口
    • 没什么特别的,实现了submit()方法,invokeAll() / invokeAny()方法,见上
  • ThreadPoolExecutor实现类:继承AbstractExecutorService抽象类,最常用的线程池实现类,下文分析

4.2 ThreadPoolExecutor

  • 属性
ctl
  • private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  • ​状态控制属性
    • 高3位表示线程池的运行状态
    • 剩下的29位表示当前有效的线程数量
worker相关
  • private final class Worker 继承AQS,实现Runnable
  • private final HashSet<Worker> workers = new HashSet<Worker>();
锁相关
  • private final ReentrantLock mainLock = new ReentrantLock();
  • private final Condition termination = mainLock.newCondition();
  • 独占锁,控制新增Worker操作的原子性
  • 中止条件:Condition类
构造器参数相关
  • private volatile int corePoolSize;
  • private volatile int maximumPoolSize;
  • private volatile long keepAliveTime;
  • private final BlockingQueue<Runnable> workQueue;
  • private volatile ThreadFactory threadFactory;
  • private volatile RejectedExecutionHandler handler;
  • 见下文构造器
静态内部类
  • static class CallerRunsPolicy; 
  • static class AbortPolicy; //默认策略
  • static class DiscardPolicy;  
  • static class DiscardOldestPolicy
  • 都实现了RejectedExecutionHandler,提供饱和策略
监控线程池的属性
  • private long completedTaskCount:线程池在运行过程中已完成的任务数量
  • private int largestPoolSize:线程池里曾经创建过的最大线程数量
  • getPoolSize():线程池的线程数量
  • getActiveCount():获取活动的线程数
  • 可以通过线程池提供的参数进行监控
  • 执行方法:可以使用两个方法向线程池提交任务,分别为execute()和submit()方法
    • execute()方法用于提交不需要返回值的任务,无法判断该线程是否已经执行成功
      • 入参是Runnable,不返回结果
    • submit(Callable x) 返回一个Future,可以通过Future判断该线程是否执行成功及结果
      • 入参是Callable接口,可以返回结果,并且能抛出异常
    • invokeAll(Collection<Callable> x):一次性提交多个任务,阻塞调用线程直到所有任务执行完毕,然后返回所有Future对象的列表
  • 线程池状态
    • ThreadPoolExecutor使用AtomicInteger ctl高3位来表示线程池状态,低29位表示线程池数量
      • 状态名高3位接受新任务处理阻塞队列任务说明
        RUNNING111YY刚创建线程池默认状态
        SHUTDOWN000NY不接受新任务,但会处理阻塞队列剩余任务
        STOP001NN不接受新任务,会中断正在执行的任务,并丢弃阻塞队列中的任务
        TIDYING010//所有任务都已终止,工作线程数为零,线程转换到状态 TIDYING 将会执行钩子函数 terminated()
        TERMINATED011//terminated() 执行完毕后,进入终结状态
    • 为何要把线程池状态(3bits)和数量(29bits)合并在一起:保证原子性,即可以只用1次CAS操作进行赋值
    • 从数字上比较TERMINATED(3)>TIDYING(2)>STOP(1)>SHUTDOWN(0)>RUNNING(-1)
  • 构造器 / 参数
    • public ThreadPoolExecutor(  int corePoolSize,    //核心线程数目(最多保留的线程数)
                                  int maxmumPoolSize,     //最大线程数
                                  long keepAliveTime,    //生存时间-针对救急线程
                                  TimeUnit unit,        //时间单位-针对救急线程
                                  BlockingQueue<Runnable> workQueue,  //阻塞队列
                                  ThreadFactory threadfactory,    //线程工厂-可以为线程取个好名字
                                  RejectedExecutionHandler handler)    //拒绝策略
  • 线程池运行机制
    • 1. 线程池刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务
    • 2. 当线程数达到corePoolSize并且没有线程空闲,这时再加入新任务,新任务会进入workQueue进行排队,直到有空闲线程
    • 3. 如果队列选择了有界队列,那么任务超过了队列大小时候,会创建maximumPoolSize- corePoolSize数目的线程来救急
    • 4. 如果线程到达maximumPoolSize仍然有新任务,这时会进行拒绝策略,jdk提供了4种实现
      • AbortPolicy(默认策略):让调用者抛出RejectedExecutionException异常
      • CallerRunsPolicy:使用调用者所在的线程来执行任务
      • DiscardPolicy:放弃本次任务,不处理,丢弃掉
      • DiscardOldestPolicy:放弃队列中最早的任务,本任务取代之
    • 5. 当高峰过去后,超过corePoolSize的急救线程如果一段时间没有任务,需要结束节省资源,时间由keepAliveTime和unit控制
  • 阻塞队列workQueue
    • 没有容量的队列
      • SynchronousQueue:不存储元素的队列,线程数到达corePoolSize后直接开始创建新线程,直至到达maximumPoolSize
    • 有界队列
      • ArrayBlockingQueue(常用):数组结构组成的有界阻塞队列(它的容量在初始化时指定,并且在生命周期中不可改变),按FIFO原则
    • 无界队列
      • LinkedBlockingQueue(常用):链表结构组成的可选无界阻塞队列(也可设置成有界队列,取决于初始化时是否指定容量),按FIFO原则,吞吐量通常要高于ArrayBlockingQueue
      • PriorityBlockingQueue:与LinkedBlockingQueue的不同在于,一个根据构造时传入的Comparator进行排序,一个根据FIFO排序
  • Worker类
    • ThreadPoolExecutor类中,一个关键的私有内部类
    • 主要特征
      • Worker类的性质:Worker类继承自AbstractQueuedSynchronizer(AQS)并实现了Runnable接口,它使用AQS来控制线程的状态
      • 封装线程:Worker类封装了线程的执行逻辑,每个Worker实例都包含一个Thread对象,它代表了线程池中的一个线程
      • 任务执行:Worker的run方法中会调用外部ThreadPoolExecutor的runWorker方法,这是线程池中执行任务的核心逻辑

4.3 Executors工具类提供的线程池实现

JUC tools下的Executors类提供了几个线程池

(1)nexFixedThreadPool:固定大小的线程池

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor( nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
}
ExecutorService pool = Executors.newFixedThreadPool(2);

pool.executor(()->{
    //任务1
});

pool.executor(()->{
    //任务2
});

pool.executor(()->{
    //任务3
});
  • 特点
    • 核心线程数=最大线程数,即没有救急线程,因此也无需超时时间
    • 阻塞队列是无界的,可以放任意数量的任务
  • 评价
    • 适用于任务数量已知,相对耗时的任务

(2)newCachedThreadPool:带缓冲池的线程池

public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}
  • 特点
    • 核心线程数是0,最大线程数MAX_VALUE,即所有创建的线程都是救急线程(60s后可以回收),救急线程可以无限创建。
    • 队列采用了SychronousQueue,实现特点是:没有容量,没有线程来取得时候是放不进去的。
  • 评价
    • 整个线程池表现为线程数会根据任务量不断增长,没有上限。当任务执行完毕,空闲线程1分钟后会释放。
    • 适合任务数较为密集,但每个任务执行时间较短的情况

(3)newSingleThreadExecutor:单线程线程池

  • 特点
    • 希望多个任务排队执行。线程数固定为1,当任务大于1时,会进入无界队列排队。执行完毕后,这唯一的线程也不会释放
    • Executors.newSingleThreadExecutor()的线程个数始终为1,不能修改
    • 它运用了装饰器模式,返回的是FinalizableDelegatedExecutorService对象不能调用ThreadPoolExecutor中特有的方法
  • 区别
    • 对比自己创建的串行执行的单线程任务,如果任务失败会终止。而该线程池会继续往下执行
    • 对比Executors.newFixedThreadPool(1),可以用setCorePoolSize来修改数量

(4)ScheduledThreadPoolExecutor:定时任务线程池

  • 特点:
    • 该线程池可以执行定时任务和周期性任务
    • 可以通过schedule()方法来提交定时任务
    • 通过scheduleAtFixedRate()或scheduleWithFixedDelay()方法来提交周期性任务

4.4 其他

(1)submit示例:

ExecutorService pool = Executors.nexFixedThreadPool(2);



//实例1:submit方法中提供Callable类
Future<String> future = pool.submit(new Callable<String>(){
    
    @Override
    public String call() throws Exception{
        Thread.sleep(1);
        return "123456789";
    }
});

//实例2:利用lambda函数
Future<String> future1 = pool.submit(()->{
    Thread.sleep(1);
    return "987654321";
});

//利用future.get()方法获取返回值
String ans = future.get();
String ans1 = future1.get();

(2)invokeAll()示例:

ExecutorService pool = Executors.newFixedThreadPool(2);

List<Future<String>> futures = pool.invokeAll(Arrays.asList(

    () -> { 
        Thread.sleep(1);
        return "线程1";
    },

    () -> { 
        Thread.sleep(1);
        return "线程2";
    },  

    () -> { 
        Thread.sleep(1);
        return "线程3";
    }
));

for(Future<String> f: futures){
    try{
    System.out.println(f.get());
    }catch(InterruptedException | ExecutionException e){
        e.printStackTrace();
    }
}

(3)invokeAny()示例:

ExecutorService pool = Executors.newFixedThreadPool(3);

String str = pool.invokeAny(Arrays.asList(
    
    () -> { 
        Thread.sleep(3);
        return "线程1";
    },

    () -> { 
        Thread.sleep(2);
        return "线程2";
    },  

    () -> { 
        Thread.sleep(1);
        return "线程3";
    }

));

//str = 线程2;因为线程2先执行完;线程1和线程3会被取消

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 好的,以下是用 JavaThreadPoolExecutor 创建线程池的示例代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 2; int maximumPoolSize = 4; long keepAliveTime = 10; ExecutorService executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, java.util.concurrent.TimeUnit.SECONDS, new java.util.concurrent.LinkedBlockingQueue<Runnable>() ); // 提交任务 for (int i = 0; i < 10; i++) { executor.execute(new Task(i)); } // 关闭线程池 executor.shutdown(); } static class Task implements Runnable { private int id; public Task(int id) { this.id = id; } public void run() { System.out.println("Task " + id + " is running."); } } } ``` 在这个例子中,我们使用 ThreadPoolExecutor 创建了一个线程池。它的参数包括: - corePoolSize:线程池中核心线程的数量; - maximumPoolSize:线程池中最大线程数; - keepAliveTime:线程池中非核心线程的超时时间; - TimeUnit:超时时间的单位; - BlockingQueue:用于缓存等待执行的任务的队列。 我们还定义了一个 Task 类来模拟需要执行的任务。在主函数中,我们提交了 10 个任务给线程池,并在任务执行结束后关闭了线程池。 ### 回答2: 使用java中的ThreadPoolExecutor类可以很方便地创建线程池。首先,我们需要导入java.util.concurrent包,在代码中创建ThreadPoolExecutor对象。 ThreadPoolExecutor类的构造函数有多个参数,其中最重要的是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue和threadFactory。corePoolSize指定了线程池中保留的线程数,maximumPoolSize指定了线程池中允许的最大线程数。keepAliveTime和unit用于指定超过corePoolSize数量的空闲线程在被终止之前等待新任务的最长时间。workQueue用于存储等待执行的任务。threadFactory用于创建新线程。 下面是一个简单的示例代码: ``` import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 5; int maximumPoolSize = 10; long keepAliveTime = 1; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10); ThreadFactory threadFactory = Executors.defaultThreadFactory(); ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); // 添加任务到线程池 for (int i = 0; i < 20; i++) { executor.execute(new Task(i)); } // 终止线程池 executor.shutdown(); } } class Task implements Runnable { private int taskId; public Task(int taskId) { this.taskId = taskId; } @Override public void run() { System.out.println("Task " + taskId + " is running."); } } ``` 在上面的示例中,我们创建了一个ThreadPoolExecutor对象executor,它使用corePoolSize=5、maximumPoolSize=10、keepAliveTime=1秒、单位TimeUnit.SECONDS的时间单位、workQueue容量为10的ArrayBlockingQueue以及默认的线程工厂。然后,我们向线程池中添加20个任务,每个任务都会打印任务的ID。最后,我们通过executor.shutdown()方法来终止线程池。 通过使用ThreadPoolExecutor类,我们可以方便地创建和管理线程池,从而实现任务的并发执行。 ### 回答3: Java中的ThreadPoolExecutor是一个用于创建和管理线程池的类。 首先,我们需要导入java.util.concurrent包中的ThreadPoolExecutor类。接下来,我们可以使用ThreadPoolExecutor的构造函数来创建一个线程池对象。构造函数接受一些参数,如核心线程数、最大线程数、线程等待时间等。 例如,我们可以使用以下代码创建一个线程池对象: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); ``` 在上面的代码中,我们创建了一个核心线程数为5,最大线程数为10的线程池。执行的任务将被放入一个无界队列LinkedBlockingQueue中,并且线程空闲时等待的时间为60秒。 接下来,我们可以通过调用execute()方法来提交任务给线程池执行。例如,我们可以创建一个实现了Runnable接口的任务,并将其提交给线程池执行,如下所示: ``` Runnable task = new MyTask(); executor.execute(task); ``` 上述代码中,MyTask是一个实现了Runnable接口的自定义任务类。我们创建了一个MyTask对象,并使用execute()方法将其提交给线程池执行。 最后,当不再需要使用线程池时,我们可以调用shutdown()方法来关闭线程池。例如: ``` executor.shutdown(); ``` 在上述代码中,executor.shutdown()方法将使线程池停止接受新的任务,并等待所有已提交的任务执行完成后,关闭线程池。 通过使用ThreadPoolExecutor类,我们可以方便地创建和管理线程池,提高多线程程序的性能和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值