【Java】线程池ThreadPool学习与使用

线程池的自我介绍

在这里插入图片描述
如果不使用线程池,每个任务都新开一个线程处理,for 循环创建线程当任务数量上升到 1000

这样开销太大,我们希望有固定数量的线程,来执行这 1000 个线程,这样就避免了反复创建并销毁线程所带来的开销问题。

线程池的好处

  • 加快响应速度、合理利用CPU和内存、统一管理
  • 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
  • 线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行

线程池适合应用的场合

服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

线程池构造参数

在这里插入图片描述
运行流程:
1、线程池创建,准备好 core 数量的核心线程,准备接受任务
2、新的任务进来,用 core 准备好的空闲线程执行。
(1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的core 就会自己去阻塞队列获取任务执行
(2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
(3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自动销毁。最终保持到 core 大小
(4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用reject 指定的拒绝策略进行处理
3、所有的线程创建都是由指定的 factory 创建的。

面试:

一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在70 个被安排上了。剩下 30 个默认拒绝策略。

corePoolSize和maxPoolSize

corePoolSize 指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时再创建新线程去执行任务

线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量 maxPoolSize

在这里插入图片描述

如果线程数小于 corePoolSize ,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。

如果线程数等于(或大于) corePoolSize 但少于 maximumPoolSize ,则将任务放入队列。

如果队列已满,并且线程数小于 maxPoolSize ,则创建一个新线程来运行任务。

如果队列已满,并且线程数大于或等于maxPoolSize ,则拒绝该任务。

在这里插入图片描述

例子:

线程池核心池大小为5,最大池大小为10,队列为100。因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100。当队列已满时,将创建最新的线程 maxPoolSize,最多到10个线程,如果再来任务就拒绝。

增减线程的特点

  • 通过设置corePoolSize和maximumPoolSize相同就可以创建固定大小的线程池。

  • 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。

  • 通过设置maximumPoolSize为很高的值,例如Integer.MAX_ VALUE,可以允许线程池容纳任意数量的并发任务。

  • 是只有在队列填满时才 创建多于 corePoolSize 的线程,所以如果你使用的是无界队列(例如 LinkedBlockingQueue),那么线程数就不会超 corePoolSize。

keepAliveTime

如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止

ThreadFactory

新的线程是由 ThreadFactory 创建的,默认使用 Executors.defaultThreadFactory() ,创建出来的线程都在同一个线程组拥有同样的NORM_ PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory ,那么就可以改变线程名、线程组、优先级、是否是守护线程等。

WorkQueue

有 3 种最常见的队列类型

  1. 直接交接:SynchronousQueue
  2. 无界队列:LinkedBlockingQueue
  3. 有界的队列:ArrayBlockingQueue

线程池应该手动创建还是自动创建

newFixedThreadPool

由于传进去的 LinkedBlockingQueue 是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。

public class FixedThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

class Task implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

执行结果:

pool-1-thread-1
pool-1-thread-4
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2

Process finished with exit code -1

newSingleThreadExecutor

这里和上面的 newFixedThreadPool 的原理基本一样,只不过把线程数直接设置成了1,所以这也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。

public class SingleThreadExector {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

执行结果

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

Process finished with exit code -1

CachedThreadPool

可缓存线程池,无界线程池,具有自动回收多余线程的功能

这里的弊端在于第二个参数 maximumPoolSize 被设置为了 Integer.MAX_VALUE,这可能会创建数量非常多的线程甚至导致O0M。

public class CacheThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
}

执行结果

pool-1-thread-843
pool-1-thread-852
pool-1-thread-841
pool-1-thread-842
pool-1-thread-847
pool-1-thread-848
pool-1-thread-850

Process finished with exit code -1

ScheduledThreadPool

public class ScheduledThreadPoolTEst {
    public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        threadPool.schedule(new Task(), 5, TimeUnit.SECONDS); // 延迟五秒之后执行
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS); // 每隔三秒执行
    }
}

正确的创建线程池的方法要根据不同的业务场景,自己设置线程池参数,分析阿里规约的图的第二二个例子,比如我们的内存有多大,我们能不能接受任务被拒绝等等

线程池里的线程数量设定为多少比较合适?

  • CPU密集型(加密、计算hash等):最佳线程数为 CPU 核心数的 1 - 2 倍左右。

  • 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于 cpu 核心数很多倍,以 JVM 线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考 Brain Goetz 推荐的计算方法,精准一点根据不同程序去做压测

线程数 = CPU 核心数 * (1 + 平均等待时间/平均工作时间)

以上4种线程池的构造函数的参数
在这里插入图片描述

FixedThreadPool 和 SingleThreadExecutor的Queue 的队列设置成 LinkedBlockingQueue

CachedThreadPool 使用的Queue是 SynchronousQueue

ScheduledThreadPool 使用的是延迟队列 DelayedWorkQueue

停止线程池

shutdown:关闭线程池的方法之一,调用线程池的此方法后,不再接受新的任务,待所有任务都执行关闭后,进行关闭
isShutdown:线程池是否已经关闭,当调用shutdown之后,此值为true
isTerminated:线程池所有任务是否已经关闭,包括正在执行和队列中的任务都结束了
awaitTermination:相对比较弱,等待一段时间,阻塞一段时间,要是线程执行完了,为true,否则为false,可以进行线程池检测
shutdownNow:暴力关闭所有线程

拒绝策略

拒绝时机
当 Executor 关闭时,提交新任务会被拒绝。以及当 Executor 对最大线程和工作队列容量使用有限边界并且已经饱和时

  1. AbortPolicy:弃任务并抛出RejectedExecutionException异常
  2. DiscardPolicy:丢弃任务,但是不抛出异常
  3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  4. CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值