多线程(五) -- 并发工具(一) -- 线程池(二) -- ThreadPoolExecutor相关内容

接上篇线程池概述及自定义线程池

3.ThreadPoolExecutor

在这里插入图片描述

3.1 线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
在这里插入图片描述
从数字上比较(第一位是符号位),TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING

这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作 进行赋值

// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
3.1.1 RUNNING
  • 状态说明:线程池处于RUNNING状态时,能够接收新任务以及对已添加的任务进行处理。
  • 状态切换:线程池的初始状态为RUNNING。换句话说线程池一旦被创建,就处于RUNNING状态,且线程池中的任务数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
3.1.2 SHUTDOWN
  • 状态说明:线程池处于SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
  • 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN
3.1.3 STOP
  • 状态说明:线程池处于STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
  • 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING)或者(SHUTDOWN)->STOP
3.1.4 TIDYING
  • 状态说明:当所有的任务已终止,ctl记录的任务数为0,线程池的状态会变为TIDYING状态;当线程池的状态变为TIDYING状态时,会调用钩子函数terminated(),该方法在ThreadPoolExecutor中是空的,若用户想在线程池变为TIDYING时进行相应的处理,就需要重载terminated()函数实现
  • 状态切换:当线程池状态为SHUTDOWN时,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUTDOWN->TIDYING
    当线程池为STOP时,线程池中执行的任务为空时,就会又STOP->TIDYING
3.1.5 TERMINATED
  • 状态说明:线程池彻底终止,就会变成TERMINATED状态
  • 状态切换:线程池处于TIDYING状态时,调用terminated()就会由TIDYING->TERMINATED

3.2 ThreadPoolExecutor七个核心参数

下面来看一下ThreadPoolExecutor完整构造方法的签名,签名中包含了七个参数,是ThreadPoolExecutor的核心,对这些参数的理解、配置、调优对于使用好线程池是非常重要的。因此接下来需要逐一理解每个参数的具体作用。先看一下构造方法签名:

 public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
1、corePoolSize:核心池的大小(线程池中保留的最大线程数)

在创建了线程池之后,默认情况下,线程池中没有任何线程,而是等待有任务到来才创建线程去执行任务。即:默认情况下,在创建了线程池之后,线程池中的线程数为0,当有任务到来后就会创建一个线程去执行任务

2、maximumPoolSize:池中允许的最大线程数

这个参数表示了线程池中最多能创建的线程数量,当任务数量比corePoolSize大时,任务添加到workQueue,当workQueue满了,将继续创建线程以处理任务,maximumPoolSize表示的就是wordQueue满了,线程池中最多可以创建的线程数量

最大线程数 = 核心线程数 + 救急线程数
救急线程是有生命时间的,而核心线程没有,不会被销毁

3、keepAliveTime:救急线程存活时间

只有当线程池中的线程数大于corePoolSize时,这个参数才会起作用。当线程数大于corePoolSize时,如果有线程等待任务时间超过此最长时间,将被释放,即救急线程会被释放

4、unit:keepAliveTime时间单位
5、workQueue:阻塞队列

存储还没来得及执行的任务(候客区)

6、threadFactory:线程工厂

执行程序创建新线程时使用的工厂,并且可以设置线程名字

7、handler:拒绝策略

由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

意思就是请求队列超过了线程池的最大容量,也超过了等待队列的容量而采用的策略。

3.3 核心池,最大池和阻塞队列的关系:

  1. 当活跃的线程数 = 核心线程数,此时不再增加活跃线程数,而是往任务队列里堆积。
  2. 当任务队列堆满了,随着任务数量的增加,会在核心线程数的基础上加开救急线程。
  3. 直到活跃线程数 = 最大线程数,就不能增加线程了。

测试参考:https://www.cnblogs.com/itbac/p/11306465.html

3.3.1 阻塞队列的作用:

既阻塞任务的到达,也阻塞无任务时的处理线程

一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源

3.3.2 为什么是先添加队列,而不是先创建最大线程?

在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率,而使用阻塞队列就可以很好的缓冲,避免创建最大线程

就好比一个饭店里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低) 。10个正式工慢慢干,迟早会千完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了) ,就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)

3.4 线程池工作方式详解:

  1. 刚开始,线程池中并没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务
  2. 当线程数达到 corePoolSize时,并且没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
  3. 如果队列选择了有界队列,那么任务超过了队列大小时,会创建最多maximumPoolSize - corePoolSize 数目的线程来救急。
  4. 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 下面的前4 种实现
    1. ThreadPoolExecutor.AbortPolicy让调用者抛出 RejectedExecutionException 异常,这是默认策略
    2. ThreadPoolExecutor.CallerRunsPolicy 让调用者运行任务
    3. ThreadPoolExecutor.DiscardPolicy 放弃本次任务
    4. ThreadPoolExecutor.DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  5. 其它著名框架也提供了实现
    1. Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
    2. Netty 的实现,是创建一个新线程来执行任务
    3. ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
    4. PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
  6. 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由 keepAliveTime 和 unit 来控制。
    在这里插入图片描述

3.5 如何确定最大线程数:

3.5.1. CPU密集型:数据分析等占用CPU的

CPU支持多少最大线程数,就定义max为线程数 + 1,可以保持CPU的效率最高

+1 是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费

Runtime.getRuntime().availableProcessors()获取CPU支持线程数
3.5.2. IO密集型:Web应用程序等

判断程序中十分耗IO的线程数。

I/O 密集型运算 CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率

IO密集型线程数经典公式:

线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间 
  • 例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式 4 * 100% * 100% / 50% = 8
  • 例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用,套用公式 4 * 100% * 100% / 10% = 40
3.5.3.corePoolSize和maximumPoolSize的工作关系
  1. 池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程
  2. 池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
  3. 池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
  4. 池中线程数大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务

3.6 谈谈workQueue

上面三种线程池都提到了一个概念,workQueue,也就是排队策略。排队策略描述的是,当前线程大于corePoolSize时,线程以什么样的方式排队等待被运行。

3.6.1 排队有三种策略:直接提交、有界队列、无界队列。

关于有界队列和无界队列请看:有界无界队列

谈谈后两种,JDK使用了无界队列LinkedBlockingQueue作为WorkQueue,而不是有界队列ArrayBlockingQueue,尽管后者可以对资源进行控制,但是个人认为,使用有界队列相比无界队列有三个缺点:

  1. 使用有界队列,corePoolSize、maximumPoolSize两个参数势必要根据实际场景不断调整以求达到一个最佳,这势必给开发带来极大的麻烦,必须经过大量的性能测试。所以干脆就使用无界队列,任务永远添加到队列中,不会溢出,自然maximumPoolSize也没什么用了,只需要根据系统处理能力调整corePoolSize就可以了
  2. 防止业务突刺。尤其是在Web应用中,某些时候突然大量请求的到来都是很正常的。这时候使用无界队列,不管早晚,至少保证所有任务都能被处理到。但是使用有界队列呢?那些超出maximumPoolSize的任务直接被丢掉了,处理地慢还可以忍受,但是任务直接就不处理了,这似乎有些糟糕
  3. 不仅仅是corePoolSize和maximumPoolSize需要相互调整,有界队列的队列大小和maximumPoolSize也需要相互折衷,这也是一块比较难以控制和调整的方面

当然,最后还是那句话,就像Comparable和Comparator的对比、synchronized和ReentrantLock,再到这里的无界队列和有界队列的对比,看似都有一个的优点稍微突出一些,但是这绝不是鼓励大家使用一个而不使用另一个,任何东西都需要根据实际情况来,当然在一开始的时候可以重点考虑那些看上去优点明显一点的

3.7 四种拒绝策略:

所谓拒绝策略之前也提到过了,任务太多,超过maximumPoolSize了怎么办?当然是接不下了,接不下那只有拒绝了。拒绝的时候可以指定拒绝策略,也就是一段处理程序。

决绝策略的父接口是RejectedExecutionHandler,JDK本身在ThreadPoolExecutor里给用户提供了四种拒绝策略,看一下:

3.7.1. AbortPolicy

直接抛出一个RejectedExecutionException,这也是JDK默认的拒绝策略

3.7.2. CallerRunsPolicy

尝试直接运行被拒绝的任务(让调用者线程执行),如果线程池已经被关闭了,任务就被丢弃了

3.7.3. DiscardOldestPolicy

移除最晚的那个没有被处理的任务,然后执行被拒绝的任务。同样,如果线程池已经被关闭了,任务就被丢弃了

3.7.4. DiscardPolicy

不能执行的任务将被删除

3.8 提交任务的方法:

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果,Future的原理就是利用我们之前讲到的保护性暂停模式来接受返回结果的,主线程可以执行 FutureTask.get()方法来等待任务执行完成
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

代码示例:

public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(2);
	// method1(pool);
	// method2(pool);
    method3(pool);
}

/**
 * 带有返回结果的执行任务方法invokeAll测试
 */
private static void method2 (ExecutorService pool) {
    try {
        List<Future<Object>> futures = pool.invokeAll(
                Arrays.asList(
                        () -> {
                            log.info("11111");
                            return "1111";
                        },
                        () -> {
                            log.info("2222");
                            return "22222";
                        },
                        () -> {
                            log.info("3333");
                            return "33333";
                        }
                )
        );
        futures.forEach( f ->{
            try {
                log.info("{}",f.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
}

/**
 * 带有返回结果的执行任务方法invokeAll测试
 */
private static void method3 (ExecutorService pool) {
    try {
        Object o = null;
        try {
            o = pool.invokeAny(
                    Arrays.asList(
                            () -> {
                                log.info("11111");
                                return "1111";
                            },
                            () -> {
                                log.info("2222");
                                return "22222";
                            },
                            () -> {
                                log.info("3333");
                                return "33333";
                            }
                    )
            );
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.info("{}",o);

    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}

/**
 * 带有返回结果的执行任务方法submit测试
 */
private static void method1(ExecutorService pool) {
    Future<String> future = pool.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            log.info("23232");
            utils.sleep(1);
            return "ok";
        }
    });

    try {
        log.info("{}",future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

3.9 关闭线程池

3.9.1 shutdown
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完,包括等待队列里面的
- 此方法不会阻塞调用线程的执行
*/
void shutdown();


public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(SHUTDOWN);
        // 仅会打断空闲线程
        interruptIdleWorkers();
        onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试终结(没有运行的线程可以立刻终结)
    tryTerminate();
}
3.9.2 shutdownNow
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();


public List<Runnable> shutdownNow() {

    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(STOP);
        // 打断所有线程
        interruptWorkers();
        // 获取队列中剩余任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试终结
    tryTerminate();
    return tasks;
}
3.9.3 其他方法:
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// Java中在使用Executors线程池时,有时场景需要主线程等各子线程都运行完毕后再执行。
// 这时候就需要用到ExecutorService接口中的awaitTermination方法
// 该方法调用会被阻塞,并且在以下几种情况任意一种发生时都会导致该方法的执行: 
// 即shutdown方法被调用之后,或者参数中定义的timeout时间到达或者当前线程被打断;
// 这几种情况任意一个发生了都会导致该方法在所有任务完成之后才执行。
// 第一个参数是long类型的超时时间,第二个参数可以为该时间指定单位
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
3.9.4 测试:
public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(2);
    Future<String> future1 = pool.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            log.info("task1 running");
            utils.sleep(3);
            log.info("task1 running");
            return "1";
        }
    });    
    
    Future<String> future2 = pool.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            log.info("task2 running");
            utils.sleep(2);
            log.info("task2 running");
            return "2";
        }
    });    
    
    Future<String> future3 = pool.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            log.info("task3 running");
            utils.sleep(3);
            log.info("task3 running");
            return "3";
        }
    });

    log.info("shutdown");
    pool.shutdown();

	// List<Runnable> runnables = pool.shutdownNow();  //  任务三处于等待队列中,将会被抛弃, 任务一和任务二被打断
    try {
        // 等待一定时间才执行下面的log.info("{}",runnables);
        // 如果不使用 pool.awaitTermination()方法,那么本来调用停止方法shutdownNow 或者 shutdown
        // 是异步执行的,就是会立即返回
        pool.awaitTermination(1,TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
	// log.info("{}",runnables);
}

4.Executors创建线程池三大方法:

个人认为,线程池的重点不是ThreadPoolExecutor怎么用或者是Executors怎么用,而是在合适的场景下使用合适的线程池,所谓"合适的线程池"的意思就是,ThreadPoolExecutor的构造方法传入不同的参数,构造出不同的线程池,以满足使用的需要

这三者创建线程池的方法本质其实式调用ThreadPoolExecutor来创建线程池

4.1 newSingleThreadExecutos() 单线程线程池

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

使用场景:

  1. 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
  2. 区别:
    1. 和自己创建单线程执行任务的区别:自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
    2. Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
      1. FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因 此不能调用 ThreadPoolExecutor 中特有的方法
    3. 和Executors.newFixedThreadPool(1) 初始时为1时的区别:
      1. Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改,对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

4.2 newFixedThreadPool(int nThreads) 固定大小线程池

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

特点:

  1. 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  2. 阻塞队列是无界的,可以放任意数量的任务
  3. 适用于任务量已知,相对耗时的任务

4.3 newCachedThreadPool() 无界线程池

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

特点:

  1. 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着:
    1. 全部都是救急线程(60s 后可以回收)
    2. 救急线程可以无限创建
  2. 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交 货)SynchronousQueue
  3. 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线 程。 适合任务数比较密集,但每个任务执行时间较短的情况

4.4 示例:

public static void main(String[] args) {
    ExecutorService pool1 = Executors.newSingleThreadExecutor(); // 创建单个线程
    // 创建固定大小的线程池
    ExecutorService pool2 = Executors.newFixedThreadPool(5); 
    // 可伸缩的,线程池数量随着线程数量变化,线程数量越多,线程池越大
    ExecutorService pool3 = Executors.newCachedThreadPool(); 

    try {
        for (int i = 0; i < 10; i++) {
            pool1.execute(() -> {
                System.out.println(Thread.currentThread().getName());
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        pool1.shutdown();
    }

}

单线程池结果:可以看到只有一个线程

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

指定线程池大小结果:可以看到最多5个线程

pool-2-thread-1
pool-2-thread-4
pool-2-thread-2
pool-2-thread-3
pool-2-thread-2
pool-2-thread-4
pool-2-thread-1
pool-2-thread-2
pool-2-thread-3
pool-2-thread-5

无界线程池结果:可以看到线程数量不确定

pool-3-thread-2
pool-3-thread-3
pool-3-thread-4
pool-3-thread-1
pool-3-thread-1
pool-3-thread-6
pool-3-thread-5
pool-3-thread-4
pool-3-thread-8
pool-3-thread-7

4.5 不建议使用:

阿里巴巴开发规范:
在这里插入图片描述

5.调度任务线程池

5.1 Timer(现在不用了)

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

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task1 =new TimerTask() {
        @SneakyThrows
        @Override
        public void run() {
            log.debug("task 1");
            // 要是这里抛出异常,线程2直接不能执行
            int i= 1/0;
            Thread.sleep(2);
        }
    };
    TimerTask task2 = new TimerTask() {
        @Override
        public void run() {
            log.debug("task 2");
        }
    };
    // 使用 timer 添加两个任务,希望它们都在 1s 后执行
    // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
    // 要是任务一中抛出异常,线程2直接不能执行
    timer.schedule(task1, 1000);
    timer.schedule(task2, 1000);
}

结果:

17:11:22.116 [Timer-0] DEBUG timer - task 1
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
	at com.yhx.toali.multiThread.TestTimer$1.run(TestTimer.java:25)
	at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
	at java.base/java.util.TimerThread.run(Timer.java:506)

5.2 ScheduledExecutorService

使用任务调度线程池,替换上述Timer的方式:

public static void main(String[] args) {
      ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
      // 添加两个任务,希望它们都在 1s 后执行,1000那个参数表示延迟多久
      executor.schedule(() -> {
          System.out.println("任务1,执行时间:" + new Date());
          int i = 1/0;
          System.out.println("任务1");
          try {
              System.out.println("任务1");
              Thread.sleep(2000); }
          catch (InterruptedException e) {
              System.out.println("xxxxx");
          }
      }, 1000, TimeUnit.MILLISECONDS);


      executor.schedule(() -> {
          System.out.println("任务2,执行时间:" + new Date());
      }, 1000, TimeUnit.MILLISECONDS);
}

结果:可以看出两个任务同事执行,并且第二个任务并没有被第一个任务的报错影响

任务1,执行时间:Fri Feb 18 17:16:17 CST 2022
任务2,执行时间:Fri Feb 18 17:16:17 CST 2022

5.3 定时执行:

主要有scheduleAtFixedRate和scheduleWithFixedDelay方法:

public static void main(String[] args) {
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    log.debug("start...");
    // 第一个参数是最开始的时候延迟的时间,第二个参数是执行下一次任务的时间隔,这个时间间隔包括执行打印log.debug("running...");的时间
//        pool.scheduleAtFixedRate(() -> {
//            log.debug("running...");
//        }, 1, 1, TimeUnit.SECONDS);
    
    // 如果睡眠时间被设置为s,那么任务的执行间隔变成了2s
//        pool.scheduleAtFixedRate(() -> {
//            log.debug("running...");
//            utils.sleep(2);
//        }, 1, 1, TimeUnit.SECONDS);


    /**
     * scheduleWithFixedDelay的使用,第二个参数的意思,是执行完
     * log.debug("running...");
     *  utils.sleep(2);
     * 还要再等待1s           
     */
    pool.scheduleWithFixedDelay(() -> {
        log.debug("running...");
        utils.sleep(2);
    }, 1, 1, TimeUnit.SECONDS);
}

6.正确处理线程池异常:

线程池中如果出现异常并不会展示出来,这时我们需要自己去处理这些异常

6.1 方法1:主动捉异常

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

结果:

17:28:17.268 [pool-1-thread-1] DEBUG timer - task1
17:28:17.277 [pool-1-thread-1] ERROR timer - error:
java.lang.ArithmeticException: / by zero
	at com.yhx.toali.multiThread.TestTimer.lambda$main$0(TestTimer.java:26)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:830)

6.2 方法2:使用 Future,错误信息都被封装进submit方法的返回方法中!

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

结果:

17:29:21.417 [pool-1-thread-1] DEBUG timer - task1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
	at com.yhx.toali.multiThread.TestTimer.main(TestTimer.java:25)
Caused by: java.lang.ArithmeticException: / by zero
	at com.yhx.toali.multiThread.TestTimer.lambda$main$0(TestTimer.java:22)
	at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:830)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值