线程池使用小结

线程,使用,小结 · 浏览次数 : 31

小编点评

**Java 中 Executors 线程池** **简介** Executors 是 Java 中线程池的工厂类,它可以创建不同的线程池,根据应用程序需求选择合适的线程池。 **主要方法** * `newSingleThreadExecutor()`:创建一个新的 SingleThreadExecutor 线程池。 * `submit()`:提交一个任务到线程池。 * `shutdown()`:关闭线程池。 **示例** ```java // 创建一个 SingleThreadExecutor 线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); // 提交三个任务 executorService.submit(() -> { System.out.println("Task 1 executed by thread: " + Thread.currentThread().getName()); }); executorService.submit(() -> { System.out.println("Task 2 executed by thread: " + Thread.currentThread().getName()); }); executorService.submit(() -> { System.out.println("Task 3 executed by thread: " + Thread.currentThread().getName()); }); // 关闭线程池 executorService.shutdown(); ``` **使用 SingleThreadExecutor 的优点** * 仅创建一个线程,确保任务按顺序执行。 * 避免线程池中的线程阻塞,影响程序性能。 **使用 SingleThreadExecutor 的缺点** * 线程池只有一个线程,如果任务量过大,会导致线程阻塞。 * 线程池中的线程不会自动关闭,需要手动关闭。 **其他** * 使用 `Executors.newFixedThreadPool(n)` 创建 n 个线程池,每个线程池包含 n 个线程。 * 使用 `Executors.newFixedThreadPool(1)` 创建一个单线程线程池,线程池中只有一个线程。 * 使用 `Executors.newScheduledThreadPool(n)` 创建 n 个延迟执行的任务,每隔 n 秒执行一次。

正文

转载请注明出处:

  在Java中,Executors是一个线程池的工厂类,它可以创建不同类型的线程池。下面是几种常见的Executors线程池,以及它们的使用区别:

  1. FixedThreadPool:这种类型的线程池有一个固定的线程数量,一旦线程池中的全部线程都在处理任务,那么后续提交的任务将会等待。如果应用程序需要限制线程数量,以便于限制系统资源的使用总量,适用于大量耗时较长的任务。

  2. SingleThreadExecutor:只有一个线程的线程池,适用于需要线程按顺序执行的场景。

  3. CachedThreadPool:这种类型的线程池不需要指定线程数量,它根据需要创建新线程,如果线程池中有空余线程,那么就会使用这些线程,如果没有,就会创建新线程。适用于需要快速响应但并发量不大的任务。

  4. ScheduledThreadPool:这种类型的线程池可以替代定时器,用于延迟执行或者按周期执行任务,可以通过调用schedule方法或scheduleAtFixedRate以指定的时间周期来执行任务。

  总之,不同类型的线程池适用于不同的场景。应该根据具体的应用程序需求来选择合适的线程池类型。

FixedThreadPool 线程池的使用示例

  FixedThreadPool 以创建固定数量的线程来执行任务。在使用 FixedThreadPool 时需要注意以下几点:

  1. 创建线程池时需要指定线程数量,线程数量固定,无法动态调整。
  2. FixedThreadPool 会将任务提交给空闲线程执行,如果所有线程都在执行任务,新提交的任务会被放入等待队列中。
  3. 线程池中的线程是一直存在的,如果不手动关闭线程池,它将一直占用系统资源。

  使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个包含 5 个线程的固定线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        // 提交 10 个任务给线程池执行
        for (int i = 0; i < 10; i++) {
            Runnable task = new Task(i);
            executor.execute(task);
        }
        // 关闭线程池
        executor.shutdown();
    }
    private static 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.");
        }
    }
}

  在这个示例中,我们创建了一个包含 5 个线程的 FixedThreadPool,并提交了 10 个任务给线程池执行。由于线程数量固定,所以会先执行前 5 个任务,后面 5 个任务会被放入等待队列中。最后我们调用 executor.shutdown() 方法来关闭线程池。

CachedThreadPool 缓存线程池的使用示例

  缓存线程池是指根据需要自动创建线程的线程池,如果线程池中有空闲线程,则会重复使用,如果没有则会自动创建。使用缓存线程池通常适用于需要快速响应的任务。

以下是一个例子,演示如何使用缓存线程池来执行多个任务:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("正在执行task " + taskNum);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task " + taskNum + " 执行完毕");
    }
}

public class TestThreadPool {
    public static void main(String[] args) {
        // 创建缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            // 提交任务到线程池
            cachedThreadPool.execute(new MyTask(i));
        }
        // 关闭线程池
        cachedThreadPool.shutdown();
        System.out.println("所有任务已提交");
    }
}

  在上面的代码示例中,创建了一个缓存线程池,然后提交了10个任务到线程池中。由于缓存线程池会根据需要自动创建线程,因此在执行这些任务的过程中线程数会动态增长,直到达到系统的最大线程数。任务执行完毕后,关闭线程池。

  注意: 在使用线程池时,需要及时关闭线程池以释放资源,否则会导致内存泄漏等问题。可以使用shutdown()方法来关闭线程池。如果需要让所有任务执行完后再关闭线程池,可以使用awaitTermination()来等待任务执行完毕。

ScheduledThreadPool 定时线程池的使用示例和注意事项

  定时线程池可以用来在指定时间或者周期性地执行任务,它主要的方法是schedule()scheduleAtFixedRate()。以下是一个使用定时线程池的示例,它会在3秒后执行一次任务,并且每隔2秒执行一次任务:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestScheduledThreadPool {
    public static void main(String[] args) {
        // 创建定时线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
        // 延迟3秒后执行任务
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务一:延迟3秒后执行。");
            }
        }, 3, TimeUnit.SECONDS);
        // 每隔2秒执行一次任务
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务二:每隔2秒执行一次任务。");
            }
        }, 0, 2, TimeUnit.SECONDS);
        // 关闭线程池
        // scheduledThreadPool.shutdown();
    }
}

  需要注意的一些事项如下:

  1. 在使用定时线程池时,需要使用Executors.newScheduledThreadPool()方法来创建线程池。
  2. schedule()方法用于延迟执行任务,而scheduleAtFixedRate()方法用于按照指定周期执行任务。
  3. scheduleAtFixedRate()方法中,第一个参数为要执行的任务,第二个参数为开始执行的延迟时间,第三个参数为每个任务执行的周期,第四个参数为时间单位。
  4. 在使用完定时线程池后,需要调用shutdown()方法来关闭线程池。

  当我们运行上述代码时,可以看到如下输出结果:

任务一:延迟3秒后执行。
任务二:每隔2秒执行一次任务。
任务二:每隔2秒执行一次任务。
任务二:每隔2秒执行一次任务。

  可以看到,在3秒后,第一个任务开始执行,而第二个任务会在3秒后开始执行,然后每隔2秒执行一次。如果我们需要中止任务的执行,可以使用ScheduledFuture.cancel()方法。

SingleThreadExecutor使用及注意事项

SingleThreadExecutor 只会创建一个线程,如果该线程因为异常终止,线程池会自动创建一个新的线程来代替它。

  SingleThreadExecutor 适用于需要按照顺序执行任务的场景,因为它保证任务的顺序性。

  1. SingleThreadExecutor 只有一个线程,如果这个线程出现异常或者挂起,那么整个线程池就会被卡住,无法执行任务。
  2. 线程池中的线程如果不手动关闭,会一直存在,可能会引发内存泄漏等问题。
  3. 如果任务量过大,会导致线程阻塞,影响程序性能。
  4. 线程池中的线程只有一个,如果任务执行时间过长,可能会影响其他任务的执行。
  5. SingleThreadExecutor 适用于任务量较小的场景,不适用于高并发场景。
  6. SingleThreadExecutor 执行任务时需要注意异常处理,尽量避免出现未捕获的异常导致线程池崩溃。
 下面是一个使用 SingleThreadExecutor 的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 提交任务
        executorService.submit(() -> {
            System.out.println("Task 1 executed by thread: " + Thread.currentThread().getName());
        });
        executorService.submit(() -> {
            System.out.println("Task 2 executed by thread: " + Thread.currentThread().getName());
        });
        executorService.submit(() -> {
            System.out.println("Task 3 executed by thread: " + Thread.currentThread().getName());
        });
        // 关闭线程池
        executorService.shutdown();
    }
}

  在上面的示例代码中,我们首先通过 Executors.newSingleThreadExecutor() 创建了一个 SingleThreadExecutor 线程池,然后通过 executorService.submit() 方法提交了三个任务。由于 SingleThreadExecutor 只有一个线程,因此这些任务会依次被执行,输出结果类似于:

Task 1 executed by thread: pool-1-thread-1
Task 2 executed by thread: pool-1-thread-1
Task 3 executed by thread: pool-1-thread-1

  最后,我们通过 executorService.shutdown() 方法关闭了线程池。需要注意的是,在使用 SingleThreadExecutor 时,如果没有手动关闭线程池,线程池中的线程会一直存在,可能会引发内存泄漏等问题。因此,我们需要在适当的时候手动关闭线程池。

 

与线程池使用小结相似的内容:

线程池使用小结

转载请注明出处: 在Java中,Executors是一个线程池的工厂类,它可以创建不同类型的线程池。下面是几种常见的Executors线程池,以及它们的使用区别: FixedThreadPool:这种类型的线程池有一个固定的线程数量,一旦线程池中的全部线程都在处理任务,那么后续提交的任务将会等待。如

[转帖]线程池使用的10个坑

https://juejin.cn/post/7132263894801711117 前言 大家好,我是捡田螺的小男孩。 日常开发中,为了更好管理线程资源,减少创建线程和销毁线程的资源损耗,我们会使用线程池来执行一些异步任务。但是线程池使用不当,就可能会引发生产事故。今天田螺哥跟大家聊聊线程池的10

面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!

写在开头 面试官:“小伙子,线程池使用过吗,来聊一聊它吧!” 我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问...” 面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?” 我:“知道知道,直接调用shutdownNow()方法就好了呀!” 面试官脸色一变,微怒道

[转帖]一文浅析Nginx线程池!

https://zhuanlan.zhihu.com/p/616500765 Nginx通过使用多路复用IO(如Linux的epoll、FreeBSD的kqueue等)技术很好的解决了c10k问题,但前提是Nginx的请求不能有阻塞操作,否则将会导致整个Nginx进程停止服务。 但很多时候阻塞操作是

小米面试:如何实现优先级线程池?

我们知道,线程池中的所有线程都是由统一的线程工厂来创建的,当我们指定线程工厂时,线程池中的所有线程会使用我们指定的线程工厂来创建线程;但如果没有指定线程工厂,则会使用默认的线程工厂 DefaultThreadFactory 来创建线程,核心源码如下: DefaultThreadFactory() {

[转帖]Java线程的5个使用技巧

https://cloud.tencent.com/developer/article/1179560?from=article.detail.1767994&areaSource=106000.6&traceId=akXSS578NgvCLH6Eiqbla Java线程有哪些不太为人所知的技巧与用

[转帖]Redis客户端Jedis、Lettuce、Redisson

https://www.jianshu.com/p/90a9e2eccd73 在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce Jedis:采用的直连,BIO网络模型 Jedis有一个问题:多个线程使用一个连接的时候线程不安全。 解决思路是: 使用连接池,为每个请求创建

Java21的虚拟线程Virtual Thread初体验

我们之前使用的是操作系统平台的线程,就称之为“系统线程”吧。虚拟线程是JDK维护的,原理跟WebFlux的底层实现差不多,都是工作线程分离。 要使用虚拟线程,需要使用JDK21以上,包括21。 虚拟线程可以创建很多很多 系统线程不能轻易创建太多,我们一直被教导创建线程是很重的活动。 for (int

[转帖]Redis 性能优化的 13 条军规!史上最全

https://zhuanlan.zhihu.com/p/118532234 Redis性能优化实战方案 Redis 是基于单线程模型实现的,也就是 Redis 是使用一个线程来处理所有的客户端请求的,尽管 Redis 使用了非阻塞式 IO,并且对各种命令都做了优化(大部分命令操作时间复杂度都是 O

[转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术。动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就获得丰富的信息,帮你分析、定位想要排查的问题。 以往,在排查和调试性能问题时,我们往往需要先为应用程