线程池

主要用于概念扫盲和梳理。
本文主要为什么使用线程池,ThreadPoolExecutor构造函数,常见的线程池种类。

一. 为什么用线程池

1. 系统开销

创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率

例如:
记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

线程池缓存线程,可复用的闲置线程来执行新任务,避免了T1+T3带来的系统开销

2. 系统资源

线程并发数量过多,抢占系统资源从而导致阻塞

我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况

运用线程池能有效的控制线程最大并发数,避免以上的问题

3. 管理线程

对线程进行一些简单的管理

比如:延时执行、定时循环执行的策略

运用线程池都能进行很好的实现>

二. 继承关系

总览

线程池

常用的几个类或接口

ExecutorService

线程池接口,继承于Executor接口,多了Submit()shutdown()等方法

ScheduledExecutorService

功能和Timer类似,解决那些需要任务重复执行的问题,负责线程的调度

ThreadPoolExecutor

线程池的实现类

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

Executors

一个工具类,提供几个创建线程池的方法

Future 接口

它和FutureTask类(实现了Future接口)都用来表示异步计算的结果。

三. 构造线程池

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。

  • 在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类
  • 对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置

构造函数

七个参数 :
心 总 时 纲 队 厂 异

五个参数的构造函数

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

六个参数的构造函数-1

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

六个参数的构造函数-2

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

七个参数的构造函数

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

参数解释

1. int corePoolSize ---核心线程数最大值

  • 线程池新建线程的时候
    1. 如果当前线程总数小于corePoolSize,则新建的是核心线程
    2. 如果超过corePoolSize,则新建的是非核心线程
  • 核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
  • 如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

2. int maximumPoolSize---线程总数最大值

  • 线程总数 = 核心线程数 + 非核心线程数

3. long keepAliveTime---非核心线程闲置超时时长

  • 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
  • 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

4. TimeUnit unit---量纲

keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:

单位 时长
NANOSECONDS 1微毫秒 = 1微秒 / 1000
MICROSECONDS 1微秒 = 1毫秒 / 1000
MILLISECONDS 1毫秒 = 1秒 /1000
SECONDS
MINUTES
HOURS 小时
DAYS

5. BlockingQueue<Runnable> workQueue--任务队列

  • 维护着等待执行的Runnable对象
  • 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

常用的workQueue类型:

SynchronousQueue

它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现线程数达到了maximumPoolSize而不能新建线程的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

LinkedBlockingQueue

基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。

由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

ArrayBlockingQueue

基于数组的先进先出队列,此队列创建时必须指定大小

可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

DelayQueue

队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

6. ThreadFactory threadFactory

主要用来创建线程,是一个接口,new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上

AsyncTask新建线程池的threadFactory参数源码:

new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);
    
    public Thread new Thread(Runnable r) {
        return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
    }
}

7. RejectedExecutionHandler handler

表示当拒绝处理任务时的策略,有以下四种取值:

取值 策略
ThreadPoolExecutor.AbortPolicy(默认) 丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy 也是丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy 由调用线程处理该任务,线程调用运行该任务的execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度

ThreadPoolExecutor的策略

上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:

  1. 核心线程满了吗
    1.1 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
    1.2 线程数量达到了corePools,则将任务移入队列等待
  2. 队列满了吗
    2.1 队列已满,新建线程(非核心线程)执行任务
    2.2 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常

总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于 corePoolSize,再看缓冲队列 workQueue 是否满,最后看线程池中的线程数量是否大于 maximumPoolSize。

另外,当线程池中的线程数量大于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。

ThreadPoolExecutor类中重要方法

方法 作用
execute() 通过这个方法可以向线程池提交一个任务,交由线程池去执行
submit() 实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果
shutdown() 不会立即终止线程池,而是要所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow() 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

四. Executor与Executors

关系

Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

对比

  • Executor 框架
    Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架
    无限制的创建线程会引起应用程序内存溢出,所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用 Executor 框架可以非常方便的创建一个线程池。

  • Executors 类
    Executors为Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池。

五. 常见四种线程池

Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的

1. CachedThreadPool()--可缓存线程池

特点

  1. 线程数无限制
  2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程
  3. 一定程度上减少频繁创建/销毁线程,减少系统开销

场合

缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的 daemon 型 SERVER 中用得不多。
但对于生存期短的异步任务,它是 Executor 的首选。

源码:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

2. FixedThreadPool(int nThreads)--定长线程池

特点

  1. 可控制线程最大并发数(同时执行的线程数)
  2. 超出的线程会在队列中等待

场合

FixedThreadPool 多数针对一些很稳定很固定的正规并发线程,多用于服务器。

//threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮

3. ScheduledThreadPool(int corePoolSize)--定核心线程池

  1. 支持定时及周期性任务执行。

创建方法:

//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

4. SingleThreadExecutor()--单线程化的线程池

  1. 有且仅有一个工作线程执行任务
  2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则

创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

源码:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

六. 实际例子

Executor 执行 Runnable 任务

通过 Executors 的以上四个静态工厂方法获得 ExecutorService 实例,而后调用该实例的 execute(Runnable command)方法即可。一旦 Runnable 任务传递到 execute()方法,该方法便会自动在一个线程上执行。

import java.util.concurrent.ExecutorService;   
import java.util.concurrent.Executors;   

public class TestCachedThreadPool{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
//      ExecutorService executorService = Executors.newFixedThreadPool(5);  
//      ExecutorService executorService = Executors.newSingleThreadExecutor();  
        for (int i = 0; i < 5; i++){   
            executorService.execute(new TestRunnable());   
            System.out.println("************* a" + i + " *************");   
        }   
        executorService.shutdown();   
    }   
}   

class TestRunnable implements Runnable{   
    public void run(){   
        System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
    }   
}

输出:(每次可能不一样)

************* a0 *************
pool-1-thread-1线程被调用了。
************* a1 *************
pool-1-thread-2线程被调用了。
************* a2 *************
pool-1-thread-3线程被调用了。
************* a3 *************
************* a4 *************
pool-1-thread-1线程被调用了。
pool-1-thread-3线程被调用了。

Executor 执行 Callable 任务

在 Java 5 之后,任务分两类:一类是实现了 Runnable 接口的类,一类是实现了 Callable 接口的类。两者都可以被 ExecutorService 执行,但是 Runnable 任务没有返回值,而 Callable 任务有返回值。并且 Callable 的 call()方法只能通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。

import java.util.ArrayList;   
import java.util.List;   
import java.util.concurrent.*;   

public class CallableDemo{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
        List<Future<String>> resultList = new ArrayList<Future<String>>();  
        for (int i = 0; i < 10; i++){   //创建10个任务并执行  
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
            Future<String> future = executorService.submit(new TaskWithResult(i));   
            //将任务执行结果存储到List中   
            resultList.add(future);   
        }   

        //遍历任务的结果   
        for (Future<String> fs : resultList){   
                try{   
                    while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                    System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{
                    executorService.shutdown();   
                }   
        }   
    }   
}   

class TaskWithResult implements Callable<String>{   
    private int id;   

    public TaskWithResult(int id){   
        this.id = id;   
    }   

    //任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行 
    public String call() throws Exception {  
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
        //该返回结果将被Future的get方法得到  
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
    }   
}  

输出:(每次可能不一样)

call()方法被自动调用!!!    pool-1-thread-1
call()方法被自动调用!!!    pool-1-thread-3
call()方法被自动调用!!!    pool-1-thread-1
call()方法被自动调用!!!    pool-1-thread-2
call()方法被自动调用!!!    pool-1-thread-8
call()方法被自动调用!!!    pool-1-thread-5
call()方法被自动调用!!!    pool-1-thread-9
call()方法被自动调用!!!    pool-1-thread-4
call()方法被自动调用!!!    pool-1-thread-6
call()方法被自动调用!!!    pool-1-thread-7
call()方法被自动调用,任务返回的结果是:0    pool-1-thread-1
call()方法被自动调用,任务返回的结果是:1    pool-1-thread-2
call()方法被自动调用,任务返回的结果是:2    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:3    pool-1-thread-4
call()方法被自动调用,任务返回的结果是:4    pool-1-thread-5
call()方法被自动调用,任务返回的结果是:5    pool-1-thread-6
call()方法被自动调用,任务返回的结果是:6    pool-1-thread-7
call()方法被自动调用,任务返回的结果是:7    pool-1-thread-8
call()方法被自动调用,任务返回的结果是:8    pool-1-thread-1
call()方法被自动调用,任务返回的结果是:9    pool-1-thread-9

参考文章
线程池,这一篇或许就够了
Java并发编程:线程池的使用
官方文档
【Java多线程】线程池的工作原理详解(上)
极客学院

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 文章摘要:在业务系统中,线程池框架技术一直是用来解决多线程并发的一种有效方法。 在JDK中,J.U.C并发包下的T...
    癫狂侠阅读 2,091评论 2 21
  • 为什么使用线程池 当我们在使用线程时,如果每次需要一个线程时都去创建一个线程,这样实现起来很简单,但是会有一个问题...
    闽越布衣阅读 4,291评论 10 45
  • 一.Java中的ThreadPoolExecutor类 java.uitl.concurrent.ThreadPo...
    谁在烽烟彼岸阅读 645评论 0 0
  • 萱草里的蛐蛐拼了命似的唱歌,清越嘹亮,一位令人倾倒的自然高音歌唱家,尤其在夜幕笼罩的安静之下,能深刻体味到其汹涌的...
    花不识阅读 837评论 8 5
  • 微信营销公众号其实就是许可证件邮件营销的加强版,账号推送相关有价值的信息给用户,用户对您的公众号感兴趣,产生关注,...
    yukina陈阅读 642评论 0 0