Java线程池详解1--概述

线程池架构

Java的线程池架构如下图所示:

1.jpg

Executor接口

该接口只提供了一个execute方法,该方法用于已提交的Runnable任务对象,该接口提供了任务提交和任务执行解耦的方法。

void execute(Runnable command);

ExecutorService接口

ExecutorService主要在Executor上做了一些扩展。

(1) 线程池关闭

// 关闭线程池,该方法执行后,拒绝接受新任务,但之前提交的任务继续执行
void shutdown();

// 立即关闭线程池,该方法执行后,停止所有正在执行及正在等待的任务,并返回等待执行的任务列表
List<Runnable> shutdownNow();

// 判断线程池是否为shutdown状态
boolean isShutdown();

// 判断线程池是否为terminated状态
boolean isTerminated();

// 在shutdown请求后,阻塞等待所有任务执行完毕
boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

(2) 异步任务提交

// 提交可执行的任务,并返回Future
// 任务执行完毕后,Future#get()方法返回任务执行的结果
<T> Future<T> submit(Callable<T> task);

// 提交可执行的任务,并返回Future
// 任务执行完毕后,Future#get()方法返回任务执行的结果给result
<T> Future<T> submit(Runnable task, T result);

// 提交可执行的任务,并返回Future
// 任务执行完毕后,Future#get()方法返回null
Future<?> submit(Runnable task);

ScheduledExecutorService

// 在给定延时后,创建并执行一个Runnable任务
// 任务执行完毕后,ScheduledFuture#get()方法会返回null
public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
// 在给定延时后,创建并执行一个Callable任务
// 任务执行完毕后,ScheduledFuture#get()方法会返回任务执行结果                                
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

// 在给定延时后,周期性创建并执行Runnable任务
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

// 在给定延时后,周期性创建并执行Runnable任务(上次任务结束时间和当前任务开始时间间隔相同)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize

线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到线程数等于corePoolSize;

如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

  • maximumPoolSize

线程池允许的最大线程数。

如果当前阻塞队列满了,且继续提交任务,则会创建新的非核心线程来执行任务,前提是当前线程数小于maximumPoolSize。

  • keepAliveTime

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。

  • workQueue

线程池阻塞队列,当线程池中的线程数目超过corePoolSize时,任务会添加到阻塞队列。

  • handler

如果当前线程数等于maximumPoolSize时,且阻塞队列也满了,若还有提交的任务,线程池需要采取一种策略处理该任务。

线程池提供4种策略:

(1) AbortPolicy: 直接抛出异常,默认策略;
(2) CallerRunsPolicy: 用调用者所在的线程执行任务;
(3) DiscardOldestPolicy: 丢弃阻塞队列中靠前的任务,并执行当前任务;
(4) DiscardPolicy: 直接丢弃任务。

常用线程池

说到线程池,首先想到的是Executors工具类,其提供了常用的几种线程池:

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

该线程池中有固定数目(N)的工作线程,可以同时运行N个任务,当提交的任务数大于N时,后提交的任务将添加到阻塞队列,等待线程池中出现空闲的工作线程。

当线程池中有工作线程宕掉后,此时,线程池会新起一个线程添加到池中,以保证线程池中的工作线程数为N。

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

线程池中仅有1个工作线程,当工作线程宕掉后,线程池会新起1个线程作为工作线程。

该线程池只能同时运行1个任务,其他任务只能在阻塞队列中等待。

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

如果线程池无可用线程,则会新建1个线程添加到线程池中。当线程池中的线程超过60秒未使用的话,会自动从线程池中移除。

可以发现,上述3种线程池均创建的是ThreadPoolExecutor。

  • 调度线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

该线程池可以再指定的时间内周期性的执行所提交的任务。

线程池状态

  • RUNNING

线程池处于RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。

  • SHUTDOWN

线程池处于SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。

调用线程池的shutdown()接口时,线程池由RUNNING-->SHUTDOWN。

  • STOP

线程池处于STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。

调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN)-->STOP。

  • TIDYING

当所有的任务已终止,ctl记录的任务数量为0,线程池会变为TIDYING状态。

当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理,可以通过重载terminated()函数来实现。

当线程池处于SHUTDOWN或STOP状态下,阻塞队列为空,并且线程池中执行的任务也为空时,就会由(SHUTDOWN or STOP)-->TIDYING。

  • TERMINATED

线程池彻底终止,就变成TERMINATED状态。

线程池处于TIDYING状态时,执行完terminated()之后,就会由TIDYING-->TERMINATED。

ThreadPoolExecutor使用一个int值c来表征线程状态和工作线程数目,前3位表征线程状态,后29位表征工作线程数目。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

将相关值转化为二进制如下:

RUNNING = 111 | 0 0000 0000 0000 0000 0000 0000 0000
SHUTDOWN = 000| 0 0000 0000 0000 0000 0000 0000 0000
STOP = 001| 0 0000 0000 0000 0000 0000 0000 0000
TIDYING = 010| 0 0000 0000 0000 0000 0000 0000 0000
TERMINATED = 011| 0 0000 0000 0000 0000 0000 0000 0000

CAPACITY = 000 | 1 1111 1111 1111 1111 1111 1111 1111
~CAPACITY = 111 | 0 0000 0000 0000 0000 0000 0000 0000

runStateOf和workerCountOf分别用于从c中解析出线程状态和工作线程数目,而ctlOf用于将线程状态值和工作线程数目值合成c。

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