Java 线程池 – ThreadPoolExecutor 示例

Java 线程池 – ThreadPoolExecutor 示例

为什么需要Java 中的线程池?答案通常是当您在 Java 中开发一个简单的并发应用程序时,您会创建一些Runnable对象,然后创建相应的Thread对象来执行它们。在 Java 中创建线程是一项昂贵的操作。而且如果你每次都开始创建新的线程实例来执行任务,应用程序的性能肯定会下降。

1.java中线程池的工作原理

线程池是预初始化线程的集合。一般来说,集合的大小是固定的,但不是强制性的。它有助于使用相同的线程执行 N 个任务。如果任务多于线程,则任务需要在类似结构的队列中等待 (FIFO - 先进先出)。

当任何线程完成其执行时,它可以从队列中取出一个新任务并执行它。当所有任务完成时,线程保持活动状态并等待线程池中的更多任务。

线程池

​ 线程池

观察者继续观察队列(通常是BlockingQueue)是否有任何新任务。一旦任务到来,线程就会再次开始接收任务并执行它们。

2. 线程池执行器

从 Java 5 开始,Java 并发 API 提供了一种机制Executor 框架。这是围绕Executor接口、它的子接口ExecutorServiceThreadPoolExecutor实现这两个接口的类。

ThreadPoolExecutor将任务创建和执行分开。使用ThreadPoolExecutor,您只需实现Runnable对象并将它们发送给执行程序。它负责它们的执行、实例化和使用必要的线程运行。

它超越了这一点,并使用线程池提高了性能。当您将任务发送给执行程序时,它会尝试使用池线程来执行此任务,以避免连续产生线程。

3.如何创建ThreadPoolExecutor

java.util.concurrent.Executors我们可以在接口中使用预先构建的方法创建以下 5 种类型的线程池执行器。

  1. 固定线程池执行器——创建一个线程池,重用固定数量的线程来执行任意数量的任务。如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。它最适合大多数现实生活用例。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
    
    
  2. 缓存线程池执行器——创建一个线程池,根据需要创建新线程,但在可用时将重用以前构建的线程。如果任务长时间运行,请勿使用此线程池。如果线程数超出系统可以处理的范围,它可能会导致系统崩溃。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    
    
  3. 调度线程池执行器——创建一个线程池,可以调度命令在给定延迟后运行,或定期执行。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(10);
    
    
  4. 单线程池执行器——创建单线程来执行所有任务。当您只有一项任务要执行时使用它。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newSingleThreadExecutor();
    
    
  5. 盗用线程池执行器——创建一个线程池,该线程池维护足够的线程以支持给定的并行度级别。这里的并行度级别是指在多处理器机器中,在单个时间点用于执行给定任务的最大线程数。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newWorkStealingPool(4);
    
4. ThreadPoolExecutor 示例
4.1 创建任务

让我们创建一个任务,每次都需要随机时间来完成它。

Task.java

package com.howtodoinjava.threads;
 
import java.util.concurrent.TimeUnit;
 
public class Task implements Runnable {
    private String name;
 
    public Task(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void run() {
        try {
            Long duration = (long) (Math.random() * 10);
            System.out.println("Executing : " + name);
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
4.2. 使用线程池执行器执行任务

给定的程序创建 5 个任务并提交到执行队列。执行器使用两个线程来执行所有任务。

ThreadPoolExample.java

package com.howtodoinjava.threads;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
 
public class ThreadPoolExample 
{
    public static void main(String[] args) 
    {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
         
        for (int i = 1; i <= 5; i++) 
        {
            Task task = new Task("Task " + i);
            System.out.println("Created : " + task.getName());
 
            executor.execute(task);
        }
        executor.shutdown();
    }
}

程序输出:

Console
    
Created : Task 1
Created : Task 2
Created : Task 3
Created : Task 4
Created : Task 5
Executing : Task 1
Executing : Task 2
Executing : Task 3
Executing : Task 4
Executing : Task 5
5. ScheduledThreadPoolExecutor

当您只需要执行一个独特的任务一次时,固定线程池或缓存线程池是很好的选择。当您需要重复执行任务 N 次(N 次固定次数或固定延迟后无限次)时,您应该使用ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor提供了 4 种方法,它们提供不同的能力来重复执行任务。

  1. ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit)– 创建并执行在给定延迟后启用的任务。
  2. ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)– 创建并执行ScheduledFuture在给定延迟后启用的。
  3. ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long delay, TimeUnit unit)– 创建并执行一个周期性操作,该操作在给定initial延迟后首先启用,然后在给定delay周期内启用。如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会同时执行
  4. ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)– 创建并执行一个周期性操作,该操作在给定initial延迟后首先启用,然后在给定delay周期内启用。无论长时间运行的任务需要多少时间,delay两次执行之间都会有固定的时间间隔。
5.1 ScheduledThreadPoolExecutor 示例
ScheduledThreadPoolExecutorExample.java
    
package com.howtodoinjava.threads;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class ScheduledThreadPoolExecutorExample 
{
    public static void main(String[] args) 
    {
        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2);
         
        Task task = new Task("Repeat Task");
        System.out.println("Created : " + task.getName());
         
        executor.scheduleWithFixedDelay(task, 2, 2, TimeUnit.SECONDS);
    }
}
 
class Task implements Runnable {
    private String name;
 
    public Task(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void run() {
        System.out.println("Executing : " + name + ", Current Seconds : " + new Date().getSeconds());
    }
}

Program output:

Console
    
Created : Repeat Task
Executing : Repeat Task, Current Seconds : 36
Executing : Repeat Task, Current Seconds : 38
Executing : Repeat Task, Current Seconds : 41
Executing : Repeat Task, Current Seconds : 43
Executing : Repeat Task, Current Seconds : 45
Executing : Repeat Task, Current Seconds : 47
6. java中自定义线程池的实现

尽管 Java 通过Executor 框架具有非常强大的线程池功能。而且您不应该在没有 executor的情况下创建自己的自定义线程池。我将强烈反对任何此类尝试。但是,如果您想创建它以供自己学习,下面给出的是Java 中的此类线程池实现

CustomThreadPool.java
    
package com.howtodoinjava.threads;

import java.util.concurrent.LinkedBlockingQueue;
 
@SuppressWarnings("unused")
public class CustomThreadPool 
{
    //Thread pool size
    private final int poolSize;
     
    //Internally pool is an array
    private final WorkerThread[] workers;
     
    // FIFO ordering
    private final LinkedBlockingQueue<Runnable> queue;
 
    public CustomThreadPool(int poolSize) 
    {
        this.poolSize = poolSize;
        queue = new LinkedBlockingQueue<Runnable>();
        workers = new WorkerThread[poolSize];
 
        for (int i = 0; i < poolSize; i++) {
            workers[i] = new WorkerThread();
            workers[i].start();
        }
    }
 
    public void execute(Runnable task) {
        synchronized (queue) {
            queue.add(task);
            queue.notify();
        }
    }
 
    private class WorkerThread extends Thread {
        public void run() {
            Runnable task;
 
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            System.out.println("An error occurred while queue is waiting: " + e.getMessage());
                        }
                    }
                    task = (Runnable) queue.poll();
                }
 
                try {
                    task.run();
                } catch (RuntimeException e) {
                    System.out.println("Thread pool is interrupted due to an issue: " + e.getMessage());
                }
            }
        }
    }
 
    public void shutdown() {
        System.out.println("Shutting down thread pool");
        for (int i = 0; i < poolSize; i++) {
            workers[i] = null;
        }
    }
}

执行我们用 执行的相同任务ThreadPoolExecutor

CustomThreadPoolExample.java
    
package com.howtodoinjava.threads;
 
public class CustomThreadPoolExample 
{
    public static void main(String[] args) 
    {
        CustomThreadPool customThreadPool = new CustomThreadPool(2);
         
        for (int i = 1; i <= 5; i++) 
        {
            Task task = new Task("Task " + i);
            System.out.println("Created : " + task.getName());
 
            customThreadPool.execute(task);
        }
    }
}

程序输出:

Console
Created : Task 1
Created : Task 2
Created : Task 3
Created : Task 4
Created : Task 5
Executing : Task 1
Executing : Task 2
Executing : Task 3
Executing : Task 4
Executing : Task 5

上面是一个非常原始的线程池实现,有很多改进。但是,与其完善上面的代码,不如专注于学习Java 执行器框架

另请注意,不正确的池或队列处理也可能导致死锁资源抖动。您当然可以使用经过 Java 社区测试的 Executor 框架来避免这些问题。

七、总结
  1. 该类ThreadPoolExecutor有四个不同的构造函数,但由于它们的复杂性,Java 并发 API 提供了Executors该类来构造执行器和其他相关对象。虽然我们可以ThreadPoolExecutor直接使用其构造函数之一进行创建,但建议使用Executors该类。
  2. 我们在上面创建的缓存线程池会在需要时创建新线程来执行新任务,如果它们已经完成了它们正在运行的任务的执行,则会重用现有线程,这些任务现在可用。然而,缓存线程池有一个缺点,就是新任务的线程不断地躺着,所以如果你向这个执行器发送了太多的任务,你可能会使系统过载。这可以使用固定线程池来克服,我们将在下一个教程中学习。
  3. ThreadPoolExecutor类和一般执行者的一个关键方面是你必须明确地结束它。如果你不这样做,执行器将继续执行,程序不会结束。如果执行器没有要执行的任务,它会继续等待新任务并且不会结束执行。Java 应用程序在其所有非守护线程完成执行之前不会结束,因此,如果您不终止执行程序,您的应用程序将永远不会结束。
  4. 要向执行者表明你想要完成它,你可以使用类的shutdown()方法ThreadPoolExecutor。当 executor 完成所有挂起任务的执行时,它就完成了它的执行。调用该shutdown()方法后,如果尝试向执行器发送另一个任务,它将被拒绝,执行器将抛出RejectedExecutionException异常。
  5. 该类ThreadPoolExecutor提供了许多方法来获取有关其状态的信息。我们在示例中使用了getPoolSize()getActiveCount()getCompletedTaskCount()方法来获取有关池大小、线程数和执行器已完成任务数的信息。您还可以使用一次getLargestPoolSize()返回池中最大线程数的方法。
  6. 该类ThreadPoolExecutor还提供了与执行器的终结相关的其他方法。这些方法是:
  • shutdownNow():此方法立即关闭执行程序。它不执行挂起的任务。它返回一个包含所有这些待处理任务的列表。调用此方法时正在运行的任务会继续执行,但该方法不会等待它们完成。

  • isTerminated():如果您调用了shutdown()orshutdownNow()方法并且执行程序完成了关闭它的过程,则此方法返回 true。

  • isShutdown():如果您调用了shutdown()执行程序的方法,则此方法返回 true。

  • awaitTermination(long timeout,TimeUnitunit):该方法阻塞调用线程,直到 executor 的任务结束或发生超时。该类TimeUnit是具有以下常量的枚举:DAYS,HOURSMICROSECONDS

不执行挂起的任务。它返回一个包含所有这些待处理任务的列表。调用此方法时正在运行的任务会继续执行,但该方法不会等待它们完成。

  • isTerminated():如果您调用了shutdown()orshutdownNow()方法并且执行程序完成了关闭它的过程,则此方法返回 true。

  • isShutdown():如果您调用了shutdown()执行程序的方法,则此方法返回 true。

  • awaitTermination(long timeout,TimeUnitunit):该方法阻塞调用线程,直到 executor 的任务结束或发生超时。该类TimeUnit是具有以下常量的枚举:DAYS,HOURSMICROSECONDS

学习愉快!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

韩眀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值