JAVA之线程池详解

SegmentFault

共 4790字,需浏览 10分钟

 ·

2021-09-10 14:41

作者:橘左京

来源:SegmentFault 思否社区

前言

既然讲到了线程池,那我们就先聊一下线程,线程为何物,每一个程序为单独的一个进程,如QQ、网易云,那我们如何理解线程呢,其实在一个进程内至少包含一个线程来作为程序的最小执行单位,如迅雷同时下载多个文件。

线程池

为什么要使用线程池?线程池顾名思义是由多个线程所组成,作用就是减少线程的建立与销毁,与数据库连接池相同概念,为了减少连接与释放,从而降低消耗提升效率。

适用场景

很多小伙伴初识多线程,一直不明白多线程实际的应用,它到底应该应用在什么地方,其实还是要根据你的具体业务来决定是否适用多线程,比如展示详情页需要花费80ms,分八步查询了不同表的数据,每步占10ms,若是同步执行就需要顺序执行组装数据,这时如果引入了多线程来进行查询,只需要10ms多的时间返回数据。

创建方式

Executors类提供了六种不同的线程池创建方案,参数是方法默认的,最终都是通过ThreadPoolExecutor实例。这种方式虽然简单方便,但是也有弊端,在开发者不了解或无意中的使用可能会造成OOM。阿里手册也明确的规定不准直接使用Executors来创建线程池,要使用ThreadPoolExecutor去自定义线程池参数。

1.Executors

第一位登场的是Executors,我们先来展示一下它的方法,从图中可以看到,它给出了六种创建线程的方式,请小伙伴们根据两个一组顺序阅读。


1)newCachedThreadPool

可缓存型线程池。它的核心线程为0,但线程总数是Integer的最大值,意味着它是最大的。它使用SynchronousQueue来作为队列,不会保留任务,任务达到后直接创建线程,可能会造成OOM的发生。

2)newFixedThreadPool

定长线程池。是一个可以控制并发数量的线程池,若任务到达后线程全部占用会加入到队列当中。它使用的是LinkedBlockingQueue作为队列,是一个无界队列,任务会源源不断加入队列,有可能造成OOM的发生。

3)newScheduledThreadPool

计划型线程池。可以设置固定或延期执行任务,当线程空闲时,直接拿来使用,如果线程都被占用,则创建新的线程。它使用的是DelayedWorkQueue作为队列,这种队列能够保证只有到了时间才会执行任务。

4)newSingleThreadExecutor

单个线程的线程池。建立只有一个线程的线程池,若有多个任务进来,只有一个被执行,其他进入等待队列,遵循先进先出规则。它使用LinkedLockingQueue作为等待队列,存在造成OOM的可能。

5)newSingleThreadScheduledExecutor

周期型执行任务的单线程线程池。

6)newWorkStealingPool

工作窃取线程池。若有线程A、B,共分配了五个任务,A分配到了四个,B分配到一个,若B执行完任务A还没有执行完成,会主动的去A窃取任务进行执行。

2.ThreadPoolExecutor

第二位登场的是ThreadPoolExecutor,它可以自定义不同的参数来达到目的,其中包含七个参数,依次展示。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)


**参数解析:
1)corePoolSize:核心线程数**

  • 当核心线程未被全部创建,即使有空闲线程也不会复用,继续创建新的核心线程。

  • 核心线程为空闲状态也不会回收,除非通过allowCoreThreadTimeOut(boolean value)设置为true

  • 可在项目初始化时调用prestartCoreThread()方法预创建线程,避免执行任务时创建线程效率低。


2)maximumPoolSize:线程总数

核心线程+临时线程的总数,是线程总数的上线,在核心线程不够用的情况下,会创建临时线程协助处理任务,临时线程空闲超出规定时间则回收。

3)keepAliveTime:超时时间

4)unit:可指定过期时间的单位是秒、时,分

5)workQueue:工作队列

若没有空闲线程,新的任务会加入到工作队列等待执行,队列又分为有界与无界队列,无界队列其实也有上限,为Integer的最大值。

6)threadFactory:线程工厂

用于实现生成线程的方式,定义是否为守护线程,主要用于设置线程名称。

7)handler:拒绝策略

当等待队列已满,就会执行拒绝策略,提供了四种方式进行选择

  • ThreadPoolExecutor.AbortPlicy:抛出异常,默认策略。

  • ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但不抛出异常。

  • ThreadPoolExecutor.DsicardOldestPolicy:丢弃最早的任务,将新任务加入队列。

  • ThreadPoolExecutor.CallerRunsPolicy:由线程池所在的的线程处理任务,自己处理自己的。


使用ThreadPoolExecutor正确创建线程池

static ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("橘左京").build();

  static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(30), factory, new ThreadPoolExecutor.AbortPolicy());

    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("橘左京");
        }
    }
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        executor.submit(myThread);
    }


调用线程执行有两个方法,submit和execute,submit的作用是可以通过return获取返回值,execute是无返回值的。



点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~


- END -



浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报