Java线程池使用场景和方法分析

简介: Java线程池使用场景和方法分析

线程池的研究

前期准备程序

Executor executor = ExecutorUtils.getExecutor(2);

创建新的线程,直接在最外层Executor

new Thread(new Runnable() {
   
   
    @Override
    public void run() {
   
   
        while (true){
   
   
            executor.execute(()->{
   
   
                logger.info(Thread.currentThread().getName());
            });
            try {
   
   
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
        }
    }
}).start();

效果是每秒钟线程池内的线程交替打印。

创建新的线程,在里面sleep

new Thread(new Runnable() {
   
   
    @Override
    public void run() {
   
   
        while (true){
   
   
            executor.execute(()->{
   
   
                logger.info(Thread.currentThread().getName());
                try {
   
   
                    Thread.sleep(1*1000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
            });
        }
    }
}).start();

代码直接出现异常。

直接在外部使用执行方法

executor.execute(()->{
   
   
    logger.info(Thread.currentThread().getName());
});

只打印了一次。

直接在外部循环打印

executor.execute(()->{
   
   
    while (true){
   
   
        logger.info(Thread.currentThread().getName());
        try {
   
   
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
    }
});

只用到了一个线程。从这个实验我们可以得出一个结论,线程池的一堆线程是并发的时候使用的,如果没有形成并发,就不会使用新的线程。 所以我进行了接下来的尝试。

同时直接在外面调用两个外部循环打印

executor.execute(()->{
   
   
    while (true){
   
   
        logger.info(Thread.currentThread().getName());
        try {
   
   
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
    }
});
executor.execute(()->{
   
   
    while (true){
   
   
        logger.info(Thread.currentThread().getName());
        try {
   
   
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
    }
});

在这里插入图片描述

可以发现这就是并发的了。那么如果我想只在一个execute调用里面实现这样的功能该怎么做呢?

总结

Executor.execute仅仅只是执行代码,如果想要并发执行,只能进行多次调用。即,下面为完整的例子:

PriorityBlockingQueue<Integer> task = new PriorityBlockingQueue<>();
Executor executor = ExecutorUtils.getExecutor(2);
new Thread(new Runnable() {
   
   
    @Override
    public void run() {
   
   
        while (true){
   
   
            try {
   
   
                Thread.sleep(2*100);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            task.add(new Double(Math.random()*10000).intValue());
        }
    }
}).start();
for (int i=0;i<2;i++){
   
   
    executor.execute(() -> {
   
   
        while (true) {
   
   
            Integer value = null;
            try {
   
   
                value = task.take();
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            if (Objects.nonNull(value)){
   
   
                logger.info(value);
                try {
   
   
                    Thread.sleep(MathUtils.getPositiveRandomInt(3)*1000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
            }
        }
    });
}

我用一个线程每隔200毫秒向队列里面丢一个数字,然后用线程池去取。然后executor.execute通过循环调用两次。就可以实现抢占式线程池调用了。

得想想上面的代码如何工具化。

批量执行任务集

ExecutorService service = ExecutorUtils.getExecutorService(2);
    List<Callable<Integer>> listTask = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
   
   
        listTask.add(() -> {
   
   
            int l = MathUtils.getPositiveRandomInt(100);
            int r = MathUtils.getPositiveRandomInt(100);
            Integer value = MathUtils.getRandomInt(Math.min(l, r), Math.max(l, r));
            logger.info(value);
            Thread.sleep(value);
            return value;
        });
    }
    try {
   
   
        List<Future<Integer>> futures = service.invokeAll(listTask);
        logger.info("------------------------------------");
        for (int i=0;i<100;i++){
   
   
            try {
   
   
                logger.info(futures.get(i).get());
            } catch (Exception e) {
   
   
                e.printStackTrace();
            }
        }
        service.shutdown();
    } catch (InterruptedException e) {
   
   
        e.printStackTrace();
    }
}

这种方式适合于批量执行任务,但是事先需要确定出总的任务量,等到执行完了再出结果。
通过

List<Future<Integer>> futures = service.invokeAll(listTask);

得到返回值,可以做一些合并操作。该方法适用于放在逐层局部需要进行并发执行的过程,例如爬虫,爬取一个网站的时候可以考虑逐层爬取,就可以使用该方法来做。

总结

当我们需要使用线程池的时候,如果不确定需要处理的对象有多少,就采用队列的方式,利用阻塞队列和抢占式的方法来实现线程池。如果对于每一次并发任务,知道

目录
相关文章
|
8天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
41 4
|
19天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
39 17
|
13天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
18 2
|
14天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2
|
16天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
21天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
14 2
|
5天前
三种线程的使用场景
三种创建多线程的使用场景 1、继承的方式:适合于这个任务只想被一个线程的对象执行的情况 2、实现Runnable接口方式:适合于一个任务想被多个线程执行的情况 3、实现Callable接口方式:也适合一个任务想被多个线程执行的情况,你还想得倒任务的执行结果
10 0
|
14天前
|
Java Spring
JAVA获取重定向地址URL的两种方法
【10月更文挑战第17天】本文介绍了两种在Java中获取HTTP响应头中的Location字段的方法:一种是使用HttpURLConnection,另一种是使用Spring的RestTemplate。通过设置连接超时和禁用自动重定向,确保请求按预期执行。此外,还提供了一个自定义的`NoRedirectSimpleClientHttpRequestFactory`类,用于禁用RestTemplate的自动重定向功能。
|
20天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
18 0
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。