二十二. 并发实战-ThreadPoolExecutor使用

前言

本篇文章将对线程池ThreadPoolExecutor的使用进行说明。

正文

一. 执行无返回值任务

通过ThreadPoolExecutorexecute()方法,能执行Runnable任务,示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void ThreadPoolExecutor执行简单无返回值任务() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建两个任务
        Runnable firstRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("第一个任务执行");
            }
        };
        Runnable secondRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("第二个任务执行");
            }
        };

        // 让线程池执行任务
        threadPoolExecutor.execute(firstRunnable);
        threadPoolExecutor.execute(secondRunnable);

        // 让主线程睡眠1秒,等待线程池中的任务被执行完毕
        Thread.sleep(1000);
    }
    
}

运行测试程序,结果如下。

在这里插入图片描述

二. 执行有返回值任务

通过ThreadPoolExecutorsubmit()方法,能够执行Callable任务,通过submit()方法返回的RunnableFuture能够拿到异步执行的结果。示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void ThreadPoolExecutor执行简单有返回值任务() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建两个任务,任务执行完有返回值
        Callable<String> firstCallable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "第一个任务返回值";
            }
        };
        Callable<String> secondCallable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "第二个任务返回值";
            }
        };

        // 让线程池执行任务
        Future<String> firstFuture = threadPoolExecutor.submit(firstCallable);
        Future<String> secondFuture = threadPoolExecutor.submit(secondCallable);

        // 获取执行结果,拿不到结果会阻塞在get()方法上
        System.out.println(firstFuture.get());
        System.out.println(secondFuture.get());
    }

}

运行测试程序,结果如下。

在这里插入图片描述

三. 执行有返回值任务时抛出错误

如果ThreadPoolExecutor在执行Callable任务时,在Callable任务中抛出了异常并且没有捕获,那么这个异常是可以通过Futureget()方法感知到的。示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void ThreadPoolExecutor执行简单有返回值任务时抛出错误() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建一个任务,任务有返回值,但是执行过程中抛出异常
        Callable<String> exceptionCallable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new RuntimeException("发生了异常");
            }
        };

        // 让线程池执行任务
        Future<String> exceptionFuture = threadPoolExecutor.submit(exceptionCallable);

        try {
            System.out.println(exceptionFuture.get());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}

运行测试程序,结果如下。

在这里插入图片描述

四. ThreadPoolExecutor通过submit方式执行Runnable

ThreadPoolExecutor可以通过submit()方法来运行Runnable任务,并且还可以异步获取执行结果。示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void ThreadPoolExecutor通过submit的方式来提交并执行Runnable() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建结果对象
        MyResult myResult = new MyResult();
        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myResult.setResult("任务执行了");
            }
        };

        // 通过ThreadPoolExecutor的submit()方法提交Runnable
        Future<MyResult> resultFuture = threadPoolExecutor.submit(runnable, myResult);

        // 获取执行结果
        MyResult finalResult = resultFuture.get();
        // myResult和finalResult的地址实际相同
        Assert.assertEquals(myResult, finalResult);
        // 打印执行结果
        System.out.println(resultFuture.get().getResult());
    }

    private static class MyResult {
        String result;

        public MyResult() {}

        public MyResult(String result) {
            this.result = result;
        }

        public String getResult() {
            return result;
        }

        public void setResult(String result) {
            this.result = result;
        }
    }

}

运行测试程序,结果如下。

在这里插入图片描述

实际上ThreadPoolExecutorsubmit()方法无论是提交Runnable任务还是Callable任务,都是将任务封装成了RunnableFuture接口的子类FutureTask,然后调用ThreadPoolExecutorexecute()方法来执行FutureTask

五. 关闭线程池

关闭线程池可以通过ThreadPoolExecutorshutdown()方法,但是shutdown()方法不会去中断正在执行任务的线程,所以如果线程池里有Worker正在执行一个永远不会结束的任务,那么shutdown()方法是无法关闭线程池的。示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void 通过shutdown关闭线程池() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
                System.out.println(Thread.currentThread().getName() + " 被中断");
            }
        };

        // 让线程池执行任务
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);

        // 调用shutdown方法关闭线程池
        threadPoolExecutor.shutdown();

        // 等待3秒观察现象
        LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
    }

}

运行测试程序,会发现在主线程中等待3秒后,也没有得到预期的打印结果。如果上述测试程序中使用shutdownNow,则是可以得到预期打印结果的,示例如下。

public class ThreadPoolExecutorTest {

    @Test
    public void 通过shutdownNow关闭线程池() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
                System.out.println(Thread.currentThread().getName() + " 被中断");
            }
        };

        // 让线程池执行任务
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);

        // 调用shutdown方法关闭线程池
        threadPoolExecutor.shutdownNow();

        // 等待3秒观察现象
        LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
    }

}

运行测试程序,打印如下。

在这里插入图片描述

因为测试程序中的任务是响应中断的,而ThreadPoolExecutorshutdownNow()方法会中断所有Worker,所以执行shutdownNow()方法后,正在运行的任务会响应中断并结束运行,最终线程池关闭。

假如线程池中运行着一个永远不会结束的任务,且这个任务不响应中断,那么无论是shutdown()方法还是shutdownNow()方法,都是无法关闭线程池的。

总结

ThreadPoolExecutor的使用总结如下。

  1. 通过ThreadPoolExecutorexecute()方法能够执行Runnable任务;
  2. 通过ThreadPoolExecutorsubmit()方法能够执行Runnable任务和Callable任务,并且能够获取异步的执行结果;
  3. ThreadPoolExecutorsubmit()方法会返回一个Future对象(实际就是FutureTask),如果任务执行过程中发生了异常且未捕获,那么可以通过Futureget()方法感知到异常;
  4. ThreadPoolExecutorsubmit()方法无论是提交Runnable任务还是Callable任务,都是将任务封装成了RunnableFuture接口的子类FutureTask,然后调用ThreadPoolExecutorexecute()方法来执行FutureTask
  5. 关闭线程池时,如果运行的任务可以在有限时间内运行完毕,那么可以使用shutdown()方法来关闭线程池,这能够保证在关闭线程池时,正在运行的任务会顺利运行完毕;
  6. 关闭线程池时,如果运行的任务永远不会结束但是响应中断,那么可以使用shutdownNow()方法来关闭线程池,这种方式不保证任务顺利运行完毕;
  7. 如果任务永远不会结束且不响应中断,那么无论是shutdown()方法还是shutdownNow()方法,都无法关闭线程池。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值