关于线程池需要了解的事情

Java线程池的优点:

1、降低资源消耗 通过重复利用已创建的线程降低线程创建和销毁的消耗。
2、提高响应速度 当任务到达的时候,不需要等到线程创建就能立即执行。
3、提高线程的可管理性 线程时稀缺资源,使用线程池可以进行统一的分配、调优和监控。

ThreadPoolExecutor 工作原理:

1、如果当前运行的线程少于 corePoolSize,则直接创建新线程来执行任务。
2、如果运行的线程等于或多于 corePoolSize,则将任务加入 BlockingQueue
3、如果无法加入到 BlockingQueue(队列已满),则创建新的线程来处理任务。
4、如果新创建的线程将使当前运行的线程超出 maximumPoolSize,任务将被拒绝,调用饱和策略处理(RejectedExceptionHandler.rejectedException()方法),默认是AbortPolicy,会抛出Exception。

当前运行的线程的意思是:只要不是 TERMINATED 都算活着。

ThreadPoolExecutor 的创建:

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

corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其它空闲的基本线程能够执行新任务,也会创建线程,等到需要执行的任务数大于线程池基本大小时就不在创建。
workQueue(任务队列): 用于保存等待执行的任务的阻塞队列。

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列。FIFO
  • LinkedBlockingQueue:基于链表结构的阻塞队列。FIFO

maximumPoolSize(线程池最大数量):线程池允许的最大线程数。注意,如果使用了无界的任务队列,这个参数就没什么效果。
ThreadFactory:用于设置创建线程的工厂
RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,必须采用一种策略处理提交的新任务。默认策略是 AbortPolicy,表示无法处理新任务时抛出异常。

  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:用调用者所在的线程运行任务。
  • DiscardPolicy:不处理,丢弃掉
  • DiscardOldestPolicy:丢弃队列最近的一个任务,并执行当前任务。

keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的事件。
TimeUnit:保持时间单位

示例代码演示

先定义一个任务,供以后使用:

class MyRun(val name: String) : Runnable {
    override fun run() {
        println("$name is running, ${Thread.currentThread().name}")
        TimeUnit.SECONDS.sleep(3)
        println("$name is finished")
    }
}

调用线程池执行任务一:

val executor = ThreadPoolExecutor(2, 10, 30, TimeUnit.SECONDS, ArrayBlockingQueue(1))
executor.execute(MyRun("T1"))
TimeUnit.SECONDS.sleep(20)
executor.execute(MyRun("T2"))

结果:

T1 is running, pool-1-thread-1
T1 is finished
T2 is running, pool-1-thread-2
T2 is finished

分析:T1 执行完成之后,此时 pool-1-thread-1的线程所处的状态 WAITING

"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fa1c2083000 nid=0x4403 waiting on condition [0x0000700010f37000]
   java.lang.Thread.State: WAITING (parking)

因为 corePoolSize 是 2,所以面对新的任务还是会用 新的线程 pool-1-thread-2 来执行,程序结束后线程池中线程状态:

"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fa1c2083000 nid=0x4403 waiting on condition [0x0000700010f37000]
   java.lang.Thread.State: WAITING (parking)

"pool-1-thread-2" #12 prio=5 os_prio=31 tid=0x00007fa1c287e800 nid=0x320b waiting on condition [0x000070001113d000]
   java.lang.Thread.State: WAITING (parking)

调用线程池执行任务二:

val executor = ThreadPoolExecutor(2, 10, 30, TimeUnit.SECONDS, ArrayBlockingQueue(1))
executor.execute(MyRun("T1"))
executor.execute(MyRun("T2"))
TimeUnit.SECONDS.sleep(5)
executor.execute(MyRun("T3"))
executor.execute(MyRun("T4"))

执行结果:

T1 is running, pool-1-thread-1
T2 is running, pool-1-thread-2
T2 is finished
T1 is finished
T3 is running, pool-1-thread-2
T4 is running, pool-1-thread-3
T3 is finished
T4 is finished

分析: T1、T2执行完成后,线程池中线程有:pool-1-thread-1pool-1-thread-2,此时当T3提交给线程池时,会把任务存到 BlockingQueue中等待执行,再提交T4,此时有可能 BlockingQueue 是满的状态,所以又因为 maximumPoolSize 是 10,所以会新创建线程执行任务:pool-1-thread-3

当T4刚刚执行完成,我们来看一下线程池中线程的情况:

"pool-1-thread-3" #13 prio=5 os_prio=31 tid=0x00007f90bb893800 nid=0x4e0b waiting on condition [0x000070000175b000]
   java.lang.Thread.State: TIMED_WAITING (parking)

"pool-1-thread-2" #11 prio=5 os_prio=31 tid=0x00007f90bb00f000 nid=0x3f03 waiting on condition [0x0000700001555000]
   java.lang.Thread.State: TIMED_WAITING (parking)

"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007f90bb892800 nid=0x4103 waiting on condition [0x0000700001452000]
   java.lang.Thread.State: WAITING (parking)

因为,此时线程数为3,超过了corePoolSize的个数,这时候线程就会进入 TIMED_WAITING状态,直到线程数达到 corePoolSize 的个数为止。

我们再等待一段时间(超过30秒),这时候我们来看一下线程池中线程的情况:

"pool-1-thread-3" #13 prio=5 os_prio=31 tid=0x00007f90bb893800 nid=0x4e0b waiting on condition [0x000070000175b000]
   java.lang.Thread.State: WAITING (parking)

"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007f90bb892800 nid=0x4103 waiting on condition [0x0000700001452000]
   java.lang.Thread.State: WAITING (parking)

pool-1-thread-2 经过超时时间之后,会销毁。 此时线程池只剩下 pool-1-thread-3pool-1-thread-1 两条线程,维持核心线程数。

调用线程池执行任务三:

fun main() {
    val executor = ThreadPoolExecutor(2, 10, 30, TimeUnit.SECONDS, ArrayBlockingQueue(1))
    executor.execute(MyRun("T1"))
    executor.execute(MyRun("T2"))
    TimeUnit.SECONDS.sleep(5)
    executor.execute(MyRun("T3"))
    TimeUnit.SECONDS.sleep(1)    //稍微等待一下
    executor.execute(MyRun("T4"))
}

结果:

T1 is running, pool-1-thread-1
T2 is running, pool-1-thread-2
T2 is finished
T1 is finished
T3 is running, pool-1-thread-2
T4 is running, pool-1-thread-1
T3 is finished
T4 is finished

分析: 基本和上一个示例差不多,只是在T3T4提交之间,增加了一个延迟,这时候再提交T4的时候,BlockingQueue 就不为空了,所以T4提交是会放到 BlockingQueue 中等待执行。

合理配置线程池:

CPU密集型任务:应尽可能尽可能小的配置线程(否则老是切换线程上下文的话,反而总时间会边长)。例如配置 N(CPU) + 1
IO密集型任务:IO密集型任务并不是一直再执行任务,则应配置尽可能多线程(因为IO就会阻塞,线程数多的话正好可以利用CPU空闲时间,提高利用率)。例如:2 * N(CPU)。

Executors 中几种常见的线程池:

ThreadPoolExecutor 是线程池的核心 实现类,用来执行提交的任务。
ScheduledThreadPoolExecutor 是一个线程池实现类,可以在给定的延迟后运行任务。或者定期执行任务。

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

FixedThreadPool 适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,比较适用于负载较重的服务器。 任务队列使用的是 无界的 LinkedBlockingQueue,线程池大小有界。 创建用于容纳固定线程数量的池子,每个线程存活时间是无限的,当池子满了不再添加线程,如果池子中线程都在繁忙,则将新任务放入阻塞队列中。

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

是大小无界限的线程池,适用于执行很多短期异步任务的小程序,或者负载较轻的服务器。当新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue 是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有则创建一个新的线程来执行该任务。若空闲线程大于指定超时时间,则线程会被销毁。 SynchronousQueue是BlockingQueue的一种,SynchronousQueue和其他的BlockingQueue不同的是SynchronousQueue的capacity是0。即SynchronousQueue不存储任何元素。也就是说SynchronousQueue的每一次insert操作,必须等待其他线性的remove操作。而每一个remove操作也必须等待其他线程的insert操作。

也就是说SynchronousQueue的每一次insert操作,必须等待其他线性的remove操作。而每一个remove操作也必须等待其他线程的insert操作。

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

SingleThreadExecutor 适用于需要保证顺序地执行各个任务,并且在任意时候,不会有多个线程是活动的。创建了只有一个线程的线程池,是无限存活的,繁忙是会有任务塞进任务队列。

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

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
}

适用于需要多个后台线程执行周期任务。同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。

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