【并发编程】线程池

一。线程池的构造方法

public ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize,
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue<Runnable> workQueue,
 ThreadFactory threadFactory,
RejectedExecutionHandler handler)

  七大参数:

  corePoolSize 核心线程数目 (最多保留的线程数)

  maximumPoolSize 最大线程数目

  keepAliveTime 生存时间 - 针对救急线程

  unit 时间单位 - 针对救急线程

  workQueue 阻塞队列

  threadFactory 线程工厂 - 可以为线程创建时起个好名字

  handler 拒绝策略

  线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。

  当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排 队,直到有空闲的线程。

  如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线 程来救急。

  如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它 著名框架也提供了实现

    AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略 北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

    CallerRunsPolicy 让调用者运行任务

    DiscardPolicy 放弃本次任务

    DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之

    Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方 便定位问题

    Netty 的实现,是创建一个新线程来执行任务

    ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略

    PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

二。自带的创建线程池的方法

  1。newFixedThreadPool

  特点:

    核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间

    阻塞队列是无界的,可以放任意数量的任务

  适用于任务量已知,相对耗时的任务

  2。newCachedThreadPool

  特点:

    核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s

    意味着全部都是救急线程(60s 后可以回收)

    救急线程可以无限创建

    队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交 货)

  3。newSingleThreadExecutor

  使用场景:

  希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放。

  区别:

  自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一 个线程,保证池的正常工作

  Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改

    FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因 此不能调用 ThreadPoolExecutor 中特有的方法

  Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改

    对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

三。关闭线程池

  1.shutdown

  线程池状态变为 SHUTDOWN - 不会接收新任务 - 但已提交任务会执行完 - 此方法不会阻塞调用线程的执行

  2.shutdownNow

  线程池状态变为 STOP - 不会接收新任务 - 会将队列中的任务返回 - 并用 interrupt 的方式中断正在执行的任务

四。任务调度线程

  在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但 由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个 任务的延迟或异常都将会影响到之后的任务。

  但是ScheduledExecutorService不会

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 添加两个任务,希望它们都在 1s 后执行
executor.schedule(() -> {
 System.out.println("任务1,执行时间:" + new Date());
 try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
 System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);

  scheduleAtFixedRate 例子(任务执行时间超过了间隔时间):

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
 log.debug("running...");
 sleep(2);
}, 1, 1, TimeUnit.SECONDS);

  scheduleWithFixedDelay 例子,一开始,延时 1s,scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始 所 以间隔都是 3s

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleWithFixedDelay(()-> {
 log.debug("running...");
 sleep(2);
}, 1, 1, TimeUnit.SECONDS);

五。正确处理执行任务异常

  1.使用异常捕获

ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
 try {
 log.debug("task1");
 int i = 1 / 0;
 } catch (Exception e) {
 log.error("error:", e);
 }
});

  2.使用future

ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
 log.debug("task1");
 int i = 1 / 0;
 return true;
});
log.debug("result:{}", f.get());

 

 

其他:   

  fork/join

 

posted on 2022-05-15 23:42  一只萌萌哒的提莫  阅读(7)  评论(0编辑  收藏  举报