奇思妙想录 执着追求并从中得到最大欢乐的人,才是成功者。——梭罗
歌曲封面 未知作品

萌ICP备20248808号

津ICP备2023004371号-2

网站已运行 2 年 55 天 21 小时 3 分

Powered by Typecho & Sunny

2 online · 130 ms

Title

JavaEE线程池的介绍和用法

IhaveBB

·

·

28次阅读
技术分享
Article
⚠️ 本文最后更新于2024年01月25日,已经过了226天没有更新,若内容或图片失效,请留言反馈
AI摘要:JavaEE线程池是一种线程管理技术,用于减少线程创建和销毁的资源损耗,提高程序性能。线程池通过工厂模式创建,有多种类型,如固定大小、可缓存、单线程、定时执行等。创建线程池时,可以设置核心线程数、最大线程数、存活时间、任务队列、线程工厂和拒绝策略等参数。任务队列有直接提交、有界和无界三种,拒绝策略包括抛出异常、调用者运行、丢弃任务等。自定义拒绝策略需实现RejectedExecutionHandler接口。阿里巴巴推荐使用ThreadPoolExecutor创建线程池,以避免资源耗尽风险。

Powered by AISummary.

JavaEE线程池

一.简介

1.1什么是线程池

在JAVA中,虽然创建和使用一个线程非常容易,但是由于创建线程需要使用使用一定的资源,在高并发高负载的情况下,频繁的创建和销毁线程会大量小号CPU和内存资源,对程序性能会造成很大的影响,为了解决这一问题,线程池应运而生。

线程池是一种利用池化思想来实现的线程管理技术

使用类似思想的技术还有MySQL的连接池,JVM的字符串常量池等

线程池最大的好处就是减少每次启动、销毁线程的损耗并且方便管理这些资源

同时,阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

二.线程池的介绍

2.1 线程池的创建方法

线程池的创建方法一共有七种,但总体分为两类

  • 通过 Executors 工厂类创建. 创建方式比较简单, 但是定制能力有限.
  • 通过 ThreadPoolExecutor 创建. 创建方式比较复杂, 但是定制能力强

2.1.1 如何“new”一个线程池

那么我们如何创建一个线程池呢?

线程池对象不是咱们直接通过new来创建的。而是通过了一个称专门的方法,反回了一个线程池对象

下面展示了两个创建方法,图一为正确方法,图二为错误示例

image-20230917233210609

那么,这种创建线程池的方法叫什么呢?

我们称这种方式为工厂模式(设计模式),工厂模式是用来给构造方法填坑的,因为构造方法存在着一定的局限性。那么这种局限性是什么呢?

构造方法的局限性

我们考虑有一个类,期望使用笛卡尔坐标系来构建对象(极坐标)

♾️ java 代码:
    public Point(int x, int y){}
    public Point(int r, int a){}

但是这两个方法并没有构成重载,就会编译失败。

很多时候我们构造一个对象希望有多种构造方式,就需要使用多个版本的构造方法来实现

但是构造方法要求方法的名字必须是类名,不同的构造方法只能通过重载来区分

工厂设计模式的优势

使用工厂设计模式就能解决这个问题。

使用普通的方法来代替构造方法完成初始化的工作.

普通方法就可以使用方法的名字来区分了,也就不受到重载规则的约束了.

工厂设计模式的示例
♾️ java 代码:
class PointFactory(){
    public static Point makePointByXY(double x, double y){
        Point p = new Point();
        p.setX(X);
        P.setY(Y);
        return p;
    }
    public static Point makePointByA(double r,double a){
        //写一个方法
    }
}

2.1.2线程池的类型

线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):

其实Executors本质上都是对ThreadPoolExecutor进行的封装

  1. Executors.newFixedThreadPool(int nThreads): 创建一个固定大小的线程池,可以控制并发的线程数,超出的任务会在队列中等待。
  2. Executors.newCachedThreadPool(): 创建一个可缓存的线程池,如果线程数超过处理所需,会缓存一段时间后回收多余的线程,如果线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor(): 创建一个单个线程的线程池,保证任务按照先进先出的顺序执行。
  4. Executors.newScheduledThreadPool(int corePoolSize): 创建一个可以执行延迟任务的线程池,用于定时执行任务。
  5. Executors.newSingleThreadScheduledExecutor(): 创建一个单线程的可以执行延迟任务的线程池,也用于定时执行任务。
  6. Executors.newWorkStealingPool(): 创建一个工作窃取线程池,允许任务执行的顺序不确定,通常用于高度并发的场景。这个方法是在 JDK 1.8 中引入的。
  7. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置

单线程池的意义: 虽然 newSingleThreadExecutornewSingleThreadScheduledExecutor** 是单线程池,但提供了工作队列,生命周期管理,工作线程维护等功能。

2.2 ThreadPoolExecutor

2.2.1创建方式

首先,我们看一下最原始创建线程池的方式ThreadPoolExecutor

♾️ java 代码:
import java.util.Collection;
import java.util.concurrent.*;

public class Demo1 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5,10,100,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10));
        for(int i = 0; i < 10; i++) {
            int index = i;
            threadPool.execute(() -> {
                System.out.println(index + " 线程名:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

2.2.2参数介绍

ThreadPoolExecutor 最多可以设置 7 个参数:

image-20230917173402542♾️ java 代码:
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

接下来我们详细看一下七个参数具体代表什么

  1. corePoolSize:核心线程数,线程池中始终存活的线程数。

    or:正式员工的数量. (正式员工, 一旦录用, 永不辞退)

  2. maximumPoolSize: 最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。

    or:正式员工 + 临时工的数目. (临时工: 一段时间不干活, 就被辞退).

  3. keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。

    or:临时工允许的空闲时间.

  4. unit:单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间。参数 keepAliveTime 的时间单位有以下 7 种可选:

    • TimeUnit.DAYS:天
    • TimeUnit.HOURS:小时
    • TimeUnit.MINUTES:分
    • TimeUnit.SECONDS:秒
    • TimeUnit.MILLISECONDS:毫秒
    • TimeUnit.MICROSECONDS:微妙
    • TimeUnit.NANOSECONDS:纳秒
  5. workQueue:阻塞队列,用来存储线程池等待执行的任务,均为线程安全。它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种,包含以下 7 种类型:

    • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
    较常用的是 LinkedBlockingQueueSynchronous,线程池的排队策略与 BlockingQueue 有关
  6. threadFactory:创建线程的工厂, 参与具体的创建线程工作.
  7. handler:拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:

    or:拒绝策略, 如果任务量超出公司的负荷了接下来怎么处理.

    • AbortPolicy:超过负荷, 直接抛出异常.
    • CallerRunsPolicy:调用者负责处理
    • DiscardOldestPolicy:丢弃队列中最老的任务.
    • DiscardPolicy:丢弃新来的任务.
默认策略为 AbortPolicy

2.2.3线程池执行流程

ThreadPoolExecutor 关键节点的执行流程如下:

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。

    image-20230917194453623

2.3 Executors.newFixedThreadPool

FixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待

image-20230917173319228♾️ java 代码:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  • corePoolSizemaximumPoolSize 相等均为nThreads即为线程池大小固定
  • keepAliveTime = 0 该参数默认对核心线程无效,而 FixedThreadPool 全部为核心线程;
  • workQueueLinkedBlockingQueue(无界阻塞队列),队列最大值为 Integer.MAX_VALUE
♾️ java 代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo2 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName());
            }
        };
        threadPool.submit(runnable);
        threadPool.submit(runnable);
        threadPool.submit(runnable);
        threadPool.submit(runnable);
        System.out.println("主线程结束");
    }
}

上述代码:创建了一个有2个线程的线程池,但一次给它分配了4个任务,每次只能执行2个任务,所以,得执行两次。

注:submit() 方法和 execute() 方法都是执行任务的方法。它们的区别是:submit() 方法有返回值,而 execute() 方法没有返回值。
在这里我们又引出了一个问题,线程数量设置多少最为合适呢?

其实这个问题网上有很多回答,但无论说什么,那都是不正确的.

因为在没有接触到实际项目的代码之前,是无法确定的.

一个线程执行的代码主要有两类:

  1. CPU密集型,代码里主要的逻辑实在进行算术运算/逻辑判断
  2. IO密集型,代码里主要进行IO操作

假设一个线程的所有代码都是 cpu 密集型代码,这个时候,线程池的数量不应该超过 N设置的比 N 更大,这个时候,也无法提高效率了.(cpu 负载满了)此时更多的线程反而增加调度的开销
假设一个线程的所有代码都是 IO密集的.这个时候不吃 CPU,此时设置的线程数,就可以是超过 N.较大的值个核心可以通过调度的方式,来并发执行

代码不同,线程池的线程数目设置就不同.
无法知道一个代码,具体多少内容是 cpu 密集,多少内容是 IO 密集.

2.4 Executors.newCachedThreadPool

CachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

image-20230917173228591♾️ java 代码:
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制;
  • keepAliveTime = 60s,线程空闲 60s 后自动结束
  • workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为 CachedThreadPool 线程创建无限制,不会有队列等待,所以使用 SynchronousQueue
♾️ java 代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo3 {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            threadPool.execute(() -> {
                System.out.println("线程:" + Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

2.5 Executors.newSingleThreadExecutor

SingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。

image-20230917173252241♾️ java 代码:
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • corePoolSize = 1,maximumPoolSize = 1,即线程数量只能为1;
  • keepAliveTime = 0
  • workQueueLinkedBlockingQueue(无界阻塞队列),队列最大值为 1。
♾️ java 代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo4 {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int count = i;
            threadPool.execute(() -> {
                System.out.println(count + ":任务被执行");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

2.6 Executors.newScheduledThreadPool

ScheduledThreadPool:创建一个可以执行延迟任务的线程池。

♾️ java 代码:
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo5 {

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        // 安排任务在一定的延迟后执行
        scheduledExecutorService.schedule(() -> {
            System.out.println("Task 1 executed at: " + new Date());
        }, 2, TimeUnit.SECONDS);

        // 安排任务在固定的时间间隔内重复执行
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Task 2 executed at: " + new Date());
        }, 0, 5, TimeUnit.SECONDS);

        // 在此示例中,Task 1将在2秒后执行一次,而Task 2将每隔5秒执行一次

        // 等待一段时间后关闭ScheduledExecutorService

    }
}
image-20230917174505291

2.7 newSingleThreadScheduledExecutor

♾️ java 代码:
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo6 {

    public static void main(String[] args) {
        ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

        // 安排任务在一定的延迟后执行
        singleThreadScheduledExecutor.schedule(() -> {
            System.out.println("Task 1 executed at: " + new Date());
        }, 2, TimeUnit.SECONDS);

        // 安排任务在固定的时间间隔内重复执行
        singleThreadScheduledExecutor.scheduleAtFixedRate(() -> {
            System.out.println("Task 2 executed at: " + new Date());
        }, 0, 5, TimeUnit.SECONDS);

        // 在此示例中,Task 1将在2秒后执行一次,而Task 2将每隔5秒执行一次

        // 等待一段时间后关闭SingleThreadScheduledExecutor

    }
}
image-20230917174914894

2.8 newWorkStealingPool

NewWorkStealingPoo0l:创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。它是一种特殊类型的线程池,适用于处理计算密集型任务。工作窃取线程池会自动将任务分配给可用线程,并允许线程之间窃取任务以保持高效的利用。

♾️ java 代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo7 {

    public static void main(String[] args) {
        // 创建工作窃取线程池
        ExecutorService threadPool = Executors.newWorkStealingPool();

        // 提交一些任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            threadPool.submit(() -> {
                try {
                    System.out.println("Task " + taskId + " started  " + Thread.currentThread().getName());
                    // 模拟任务执行
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("Task " + taskId + " completed  " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 等待一段时间,以确保任务有足够的时间完成
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 关闭线程池        
        threadPool.shutdown();
    }
}
image-20230917192045189

二.任务队列

在上面2.2.2中的workQueue里,我们提到一共有七种任务队列。通常使用这三种任务队列,即直接提交队列(SynchronousQueue)、有界任务队列(ArrayBlockingQueue)和无界任务队列(LinkedBlockingQueue),接下来我们详细介绍下这三种队列。

  1. 直接提交队列 (SynchronousQueue):

    • 这是一个没有容量的队列,它用于在线程池的任务提交和执行之间的直接传递。
    • 每次提交一个任务,都会尝试立即执行它,如果没有空闲线程,则会尝试创建新线程。
    • 如果已经有线程数达到 maximumPoolSize,而且队列没有足够的空间,那么线程池将拒绝接收新的任务并且采取拒绝策略(例如抛出异常)。
♾️ java 代码:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 100, TimeUnit.SECONDS, new SynchronousQueue<>());
  1. 有界任务队列 (ArrayBlockingQueue):

    • 这是一个有容量限制的队列,它用于缓存等待执行的任务。
    • 当线程池的线程数量小于 corePoolSize 时,新任务会创建新线程并立即执行。
    • 当线程池的线程数量达到 corePoolSize 时,新任务会被放入队列中等待执行。
    • 如果队列已满,但线程数还未达到 maximumPoolSize,那么将会创建新线程来执行任务。
    • 如果线程数已经达到 maximumPoolSize,则根据设置的拒绝策略处理新的任务。
♾️ java 代码:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
  1. 无界任务队列 (LinkedBlockingQueue):

    • 这是一个无限容量的队列,它用于缓存等待执行的任务。
    • 线程池的线程数量将保持在 corePoolSize 的数量,不会超过。
    • 新任务会被放入队列中等待执行,而不会创建超出 corePoolSize 的新线程。
    • maximumPoolSize 参数在这种情况下是无效的,因为线程数不会超过 corePoolSize
♾️ java 代码:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

选择适当的任务队列类型取决于你的应用程序需求和性能要求。如果你需要精确控制线程数和任务执行,有界队列可能更合适。如果你想最大程度地利用系统资源,无界队列可能更适合。直接提交队列通常在需要立即执行任务的情况下使用。要记住,不同的任务队列类型可能会影响到线程池的行为和性能,所以需要根据具体的场景来选择。

四.线程拒绝策略

当使用线程池来管理并发任务时,有时候任务提交的速度可能会超过线程池的处理能力,这时就需要一种机制来处理这种情况。线程池的拒绝策略(Rejection Policy)提供了一种处理方式,用于定义当任务无法被接受和处理时应该采取的措施。

在 Java 中,线程池的拒绝策略由 RejectedExecutionHandler 接口定义,它有几种内置的实现,常见的包括:

  1. AbortPolicy(抛弃策略):这是默认的拒绝策略,当任务无法被接受时,会抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy(调用者运行策略):当任务无法被接受时,会在当前线程中执行该任务,而不会抛出异常。这样可以降低任务提交者的速度,但可能会影响到整体性能。
  3. DiscardPolicy(丢弃策略):当任务无法被接受时,将简单地丢弃该任务,不做任何处理。
  4. DiscardOldestPolicy(丢弃最旧策略):当任务无法被接受时,会丢弃队列中最旧的任务,然后尝试重新提交当前任务。

让我们结合一个示例来深入了解这些拒绝策略的工作方式。我们使用 ThreadPoolExecutor 类来创建一个线程池,并演示使用不同的拒绝策略:

♾️ java 代码:
import java.util.Date;
import java.util.concurrent.*;

public class ThreadPoolRejectionPolicyDemo {

    public static void main(String[] args) {
        // 创建线程池,核心线程数为1,最大线程数为1,队列容量为1
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1));

        // 设置拒绝策略为 CallerRunsPolicy
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 任务
        Runnable runnable = () -> {
            System.out.println("当前任务被执行, 执行时间: " + new Date() +
                    " 执行线程: " + Thread.currentThread().getName());

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        // 开启4个任务
        for (int i = 0; i < 4; i++) {
            threadPool.execute(runnable);
        }

        // 关闭线程池
        threadPool.shutdown();
    }
}
image-20230917193407171

在上面的示例中,我们创建了一个核心线程数和最大线程数都为1的线程池,并将队列容量设置为1。这意味着线程池最多只能同时执行一个任务,同时任务队列只能容纳一个等待执行的任务。

然后,我们将拒绝策略设置为 CallerRunsPolicy,这意味着当有任务无法被接受时,当前线程(任务提交者的线程)将运行该任务,而不会抛出异常。

最后,我们提交了4个任务给线程池,但由于线程池的容量有限,只有一个任务能够立即执行,其余的任务将被线程池按照拒绝策略处理。在这个示例中,我们看到被拒绝的任务被调用者线程(主线程)执行,而不是抛出异常。

五.自定义拒绝策略

自定义拒绝策略需要实现 RejectedExecutionHandler 接口,该接口有一个方法 rejectedExecution,在任务被拒绝执行时被调用。下面是一个简单的示例:

♾️ java 代码:
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class Demo9 implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义拒绝策略的处理逻辑
        System.out.println("任务被拒绝执行:" + r.toString());
        // 可以根据需求执行其他操作,例如记录日志、重新提交任务等
    }
}

或者可以这样使用匿名内部类

♾️ java 代码:
RejectedExecutionHandler customRejectionHandler = new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义拒绝策略的处理逻辑
        System.out.println("任务被拒绝执行:" + r.toString());
        // 在这里你可以执行自定义的处理操作,比如记录日志或重新提交任务
    }
};

那么如何把自定义拒绝策略应用到我们的线程池呢?

我们只需要在最后加入我们的自定义策略名称即可。

♾️ java 代码:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            2,             // 核心线程数
            4,             // 最大线程数
            10,            // 线程空闲时间
            TimeUnit.SECONDS, // 时间单位
            new LinkedBlockingQueue<>(2), // 任务队列容量为2
            customRejectionHandler // 自定义拒绝策略
        );

六.模拟实现线程池

♾️ java 代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class MyThreadPool{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000); // 1000 是队列的容量

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public MyThreadPool(int n   ) {
        for(int i = 0; i < n; i++){
            Thread t = new Thread(() -> {
                try {
                    Runnable runnable  = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(10);

        for (int i = 0; i < 1000; i++) {
            int id = i;
            myThreadPool.submit(() -> {
                System.out.println("执行任务:" + id);
            });
        }
    }

}

在这个代码中,不知道大家有没有注意到一个问题,

♾️ java 代码:
  for (int i = 0; i < 1000; i++) {
            int id = i;
            myThreadPool.submit(() -> {
                System.out.println("执行任务:" + id);
            });
        }

在这个代码里打印执行任务时,没有直接使用i,而是新创建了一个id来表示i.

这是因为i是在匿名内部类中的,但是部落了外部变量中的i

变量捕获只能捕获final,所以我们需要新建一个id,就能让其捕获的是ID不是I了

七.如何选择线池

我们来看看阿里巴巴《Java开发手册》中给我们的答案:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

所以,综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。

现在已有 0 条评论,0 人点赞
Author:IhaveBB
作者
JavaEE线程池的介绍和用法
当前文章累计共 20145 字,阅读大概需要 11 分钟。
夜爬大青山
2023年7月18日 · 0评论
探讨Runnable与Thread的区别
2023年8月13日 · 0评论
大青山野生动物园
2023年7月19日 · 0评论
Comment:共0条
发表
搜 索 消 息 足 迹
你还不曾留言过..
你还不曾留下足迹..
博主

哈喽大家好呀

本站已从原站:33mc.cn
迁移到本站:blog.nicebao.com
主域名变为:nicebao.com

不再显示
博主