ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java线程池 [打印本页]

作者: 瑞星    时间: 2024-8-29 02:17
标题: Java线程池
线程池的概念

线程池是一种基于池化技能的多线程运用形式,它预先创建了肯定数量的线程,并将这些线程放入一个容器中(即线程池)举行管理。当需要执行新的任务时,不是直接创建新的线程,而是从线程池中取出一个空闲的线程来执行这个任务。

线程池的优缺点

长处:

     缺点:

     
线程池的实现

在Java中,java.util.concurrent包提供了多种线程池的实现,如ThreadPoolExecutor、ScheduledThreadPoolExecutor等,它们都是基于ExecutorService接口的实现。通过这些线程池实现,我们可以很方便地创建和管理线程池,以满足不同的并发需求。
常见的Java线程池实现:

ThreadPoolExecutor

   这是Java中最核心、最通用的线程池实现。它提供了丰富的参数配置,如核心线程数、最大线程数、任务队列容量、线程存活时间等,允许用户根据具体需求灵活调解线程池的行为。
  ThreadPoolExecutor还支持自定义线程工厂和拒绝策略,以满足更复杂的需求。
  利用ThreadPoolExecutor实现线程池

这个示例将展示如何创建一个线程池,提交任务到线程池,并等待全部任务完成。
  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.ThreadPoolExecutor;
  4. import java.util.concurrent.TimeUnit;
  5. public class ThreadPoolExecutorExample {
  6.     public static void main(String[] args) {
  7.         // 创建一个固定大小的线程池
  8.         // 参数分别为:核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列(这里使用无界队列)
  9.         ExecutorService executorService = Executors.newFixedThreadPool(5);
  10.         // 或者直接使用ThreadPoolExecutor构造函数来创建,这样可以更灵活地配置参数
  11.         // ExecutorService executorService = new ThreadPoolExecutor(
  12.         //     5, // 核心线程数
  13.         //     10, // 最大线程数
  14.         //     60L, // 空闲线程存活时间
  15.         //     TimeUnit.SECONDS, // 时间单位
  16.         //     new java.util.concurrent.ArrayBlockingQueue<>(100) // 任务队列
  17.         // );
  18.         // 提交任务到线程池
  19.         for (int i = 0; i < 10; i++) {
  20.             final int taskId = i;
  21.             executorService.submit(() -> {
  22.                 // 模拟任务执行
  23.                 System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
  24.                 try {
  25.                     // 假设任务执行需要一些时间
  26.                     TimeUnit.SECONDS.sleep(1);
  27.                 } catch (InterruptedException e) {
  28.                     Thread.currentThread().interrupt();
  29.                 }
  30.             });
  31.         }
  32.         // 关闭线程池,不再接受新任务,但已提交的任务会继续执行
  33.         executorService.shutdown();
  34.         // 等待所有任务完成
  35.         try {
  36.             if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
  37.                 // 如果在指定时间内没有完成,则尝试停止当前正在执行的任务
  38.                 executorService.shutdownNow();
  39.                 // 等待正在执行的任务停止
  40.                 if (!executorService.awaitTermination(60, TimeUnit.SECONDS))
  41.                     System.err.println("Pool did not terminate");
  42.             }
  43.         } catch (InterruptedException ie) {
  44.             // 当前线程在等待过程中被中断
  45.             executorService.shutdownNow();
  46.             // 保存中断状态
  47.             Thread.currentThread().interrupt();
  48.         }
  49.         System.out.println("Finished all tasks");
  50.     }
  51. }
复制代码
  在这个示例中,我们首先创建了一个固定巨细的线程池,然后提交了10个任务到线程池。每个任务都简朴地打印出它的ID和执行它的线程名称,并模拟执行了一段时间(通过TimeUnit.SECONDS.sleep(1);)。末了,我们关闭了线程池,并等待全部任务完成。
  留意,在实际应用中,你大概需要根据具体需求调解线程池的配置参数,如核心线程数、最大线程数、空闲线程存活时间等。此外,对于任务队列的选择也需要根据任务的性质来决定,比如是否允许有界队列、队列的巨细等。
  
利用ThreadPoolExecutor实现线程池的优缺点

   长处:

      缺点:

    
ScheduledThreadPoolExecutor

   这是一个继承自ThreadPoolExecutor的线程池实现,专门用于在给定的延迟后运行命令,大概定期地执行命令。
  它支持调治任务在未来的某个时间点执行,大概按照指定的频率周期性执行。
  ScheduledThreadPoolExecutor实现线程池

这个示例将展示如何创建一个ScheduledThreadPoolExecutor,并提交一个周期性执行的任务。
  1. import java.util.concurrent.Executors;
  2. import java.util.concurrent.ScheduledExecutorService;
  3. import java.util.concurrent.TimeUnit;
  4. public class ScheduledThreadPoolExecutorExample {
  5.     public static void main(String[] args) {
  6.         // 创建一个ScheduledThreadPoolExecutor,其线程池大小为3
  7.         ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
  8.         // 提交一个周期性执行的任务
  9.         // 这里的任务是在控制台打印当前时间,每2秒执行一次
  10.         Runnable periodicTask = () -> {
  11.             System.out.println("执行任务: " + System.currentTimeMillis());
  12.         };
  13.         // 初始延迟为0,表示立即开始执行;之后每隔2秒执行一次
  14.         scheduledExecutorService.scheduleAtFixedRate(periodicTask, 0, 2, TimeUnit.SECONDS);
  15.         // 注意:在实际应用中,你可能需要某种方式来关闭线程池。
  16.         // 这里为了简化示例,我们没有添加关闭线程池的代码。
  17.         // 在实际应用中,你应该在适当的时候调用shutdown()或shutdownNow()方法来关闭线程池。
  18.         // 注意:这个示例中的main方法会立即返回,但ScheduledThreadPoolExecutor中的任务会继续在后台执行。
  19.         // 如果你希望main方法等待直到所有任务都完成(对于ScheduledThreadPoolExecutor来说,这通常是不现实的,
  20.         // 因为周期性任务可能会永远执行下去),你需要使用其他同步机制。
  21.         // 但在这个简单的示例中,我们不需要这样做。
  22.     }
  23. }
复制代码
  在这个示例中,我们首先创建了一个ScheduledThreadPoolExecutor,其线程池巨细为3。然后,我们定义了一个简朴的任务,该任务只是打印出当前的时间戳。我们利用scheduleAtFixedRate方法提交了这个任务,指定了初始延迟为0(表现立即开始执行),之后每隔2秒执行一次。
  请留意,这个示例中的main方法会立即返回,但ScheduledThreadPoolExecutor中的任务会继续在后台执行。在实际应用中,你大概需要在得当的时间关闭线程池,以释放资源。这可以通过调用shutdown()或shutdownNow()方法来实现。然而,在这个简朴的示例中,我们没有添加如许的代码。
  
利用ScheduledThreadPoolExecutor实现线程池的优缺点

   长处:

      缺点:

    
Executors工厂类

   虽然Executors不是一个直接的线程池实现,但它提供了一系列静态方法来创建不同类型的线程池。
  比方,Executors.newFixedThreadPool(int nThreads)用于创建一个可重用固定线程数的线程池;Executors.newSingleThreadExecutor()用于创建一个单线程的Executor,它包管全部任务都在同一个线程中按顺序执行;Executors.newCachedThreadPool()则用于创建一个根据需要创建新线程的线程池,但每个空闲线程将在60秒后自动终止。
  利用Executors工厂类实现线程池

这个示例将展示如何创建一个固定巨细的线程池,并提交一些任务到该线程池执行。
  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. public class ExecutorsExample {
  4.     public static void main(String[] args) {
  5.         // 使用Executors工厂类创建一个固定大小的线程池,这里设置线程池大小为5
  6.         ExecutorService executorService = Executors.newFixedThreadPool(5);
  7.         // 提交任务到线程池
  8.         for (int i = 0; i < 10; i++) {
  9.             final int taskId = i;
  10.             executorService.submit(() -> {
  11.                 // 这里模拟任务执行,比如打印任务ID和执行它的线程名称
  12.                 System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
  13.                 // 注意:在实际应用中,你可能需要在这里添加一些耗时的操作,比如数据库访问、文件IO等
  14.             });
  15.         }
  16.         // 关闭线程池,不再接受新任务,但已提交的任务会继续执行
  17.         // 注意:shutdown()方法不会等待线程池中的任务执行完成,它只是不再接受新任务
  18.         // 如果你需要等待所有任务完成,可以使用shutdown()后跟上awaitTermination(),或者直接使用awaitTermination(long timeout, TimeUnit unit)
  19.         // 但为了简化示例,这里只调用shutdown()
  20.         executorService.shutdown();
  21.         // 注意:在实际应用中,你可能需要添加一些逻辑来等待所有任务完成,
  22.         // 但在这个简单的示例中,我们假设主线程(即main方法)不需要等待线程池中的任务完成。
  23.     }
  24. }
复制代码
  在这个示例中,我们首先利用Executors.newFixedThreadPool(5)创建了一个固定巨细为5的线程池。然后,我们通过一个循环提交了10个任务到线程池。每个任务都简朴地打印出它的ID和执行它的线程名称。末了,我们调用了shutdown()方法来关闭线程池,这表现线程池将不再接受新的任务,但已经提交的任务会继续执行直到完成。
  请留意,这个示例中的main方法会立即返回,但线程池中的任务大概会在main方法返回之后继续执行。如果你需要等待全部任务完成,可以考虑利用awaitTermination()方法。然而,在这个简朴的示例中,我们没有包含如许的逻辑。
  
利用Executors工厂类实现线程池的优缺点

   长处:

      缺点:

    
ForkJoinPool

   ForkJoinPool是Java 7引入的一种特殊的线程池,专为执行分而治之算法(如归并排序)而筹划。
  它利用了一种称为工作窃取(work-stealing)的算法,允许线程从其他线程的队列中窃取任务来执行,从而提高了任务处理惩罚的效率和吞吐量。
  利用ForkJoinPool实现线程池

ForkJoinPool是Java 7中引入的一种特殊的线程池,它特别适用于执行分而治之(divide-and-conquer)算法的任务。
示例展示了如何利用ForkJoinPool来并行计算一组数的和:
  1. import java.util.Arrays;
  2. import java.util.concurrent.ForkJoinPool;
  3. import java.util.concurrent.RecursiveTask;
  4. // 定义一个继承自RecursiveTask的类,用于递归分割任务
  5. class SumTask extends RecursiveTask<Integer> {
  6.     private int[] numbers;
  7.     private int start;
  8.     private int end;
  9.     // 构造函数,用于初始化任务
  10.     public SumTask(int[] numbers, int start, int end) {
  11.         this.numbers = numbers;
  12.         this.start = start;
  13.         this.end = end;
  14.     }
  15.     // 递归分割任务,当任务足够小时直接计算结果
  16.     @Override
  17.     protected Integer compute() {
  18.         int length = end - start;
  19.         if (length &lt;= 1) {
  20.             return numbers[start];
  21.         }
  22.         // 将任务分割成两半
  23.         int split = start + length / 2;
  24.         SumTask leftTask = new SumTask(numbers, start, split);
  25.         SumTask rightTask = new SumTask(numbers, split, end);
  26.         // 提交子任务到ForkJoinPool
  27.         leftTask.fork();
  28.         int rightResult = rightTask.compute();
  29.         // 等待子任务完成,并合并结果
  30.         return leftTask.join() + rightResult;
  31.     }
  32.     public static void main(String[] args) {
  33.         int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  34.         ForkJoinPool pool = ForkJoinPool.commonPool(); // 使用公共的ForkJoinPool
  35.         // 提交任务
  36.         SumTask task = new SumTask(numbers, 0, numbers.length);
  37.         Integer result = pool.invoke(task); // 阻塞当前线程直到任务完成
  38.         System.out.println("Sum of numbers: " + result);
  39.         // 注意:在大多数情况下,你不需要手动关闭ForkJoinPool,
  40.         // 因为ForkJoinPool的公共实例是为了全局复用的。
  41.         // 如果你创建了自己的ForkJoinPool实例,并且不再需要它,那么你应该调用shutdown()来关闭它。
  42.     }
  43. }
复制代码
  在这个示例中,SumTask类继承自RecursiveTask<Integer>,它表现一个返回Integer类型的递归任务。我们在compute方法中实现了任务的分割和归并逻辑。然后,在main方法中,我们创建了一个ForkJoinPool的公共实例,并提交了一个SumTask任务来计算一组数的和。末了,我们打印出了计算结果。
  请留意,ForkJoinPool的公共实例(ForkJoinPool.commonPool())是为了全局复用的,因此通常不需要手动关闭它。如果你创建了自己的ForkJoinPool实例,那么在不再需要它时应该调用shutdown()方法来关闭它。
  
利用ForkJoinPool实现线程池的优缺点

   长处:

      缺点:

  
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4