线程池简介
在之前介绍Executor框架的文章中对线程池有一个初步的认识,实际上线程池这种设计思想在Java中很普遍,比如JVM中常量池,以及Web开发使用到的数据库连接池。这些池本质上还是Java中的对象池,因为池中存放的都是Java对象。回到线程池,几乎所有需要异步或者执行并发任务的程序都可以使用到线程池。使用线程池带来的好处主要包括以下几个方面:
一,提高资源利用率。由于线程池中的线程使可以重复利用的,所以达到了循环利用的目的
二,提高响应速度。由于线程的创建也是需要开销的,如果请求到来的时候可以直接使用已经创建好的线程对象自然就能提高响应速度了
三,便于对线程进行统一的管理。线程属于稀缺资源,如果无限制的创建,不仅会消耗大量的资源还会大大降低系统的稳定性。使用线程池则可以对线程进行统一的分配和监控。
线程池的实现原理
其实线程池一句话就可以概括:通过将事先创建好的线程存放起来,在需要的时候直接拿过来使用就可以了。但是,为了提高线程池的性能,实际的线程池要比这种简化版复杂得多。在前面的线程池中,都只接收Runnable和Callable的任务,或者称为工作单元。那么就来分析当一个工作单元提交到线程池的时候具体发生了什么。
- 线程池首先判断核心线程池中线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果都在执行任务,也就是没有空闲线程的话就进入下个流程
- 线程池继续判断工作队列是否已经满了。如果工作队列没有满,则把新提交的任务放入该工作队列中,如果工作队列已经满了,则进入下个流程
- 线程池判断线程池中的线程是否都处于工作状态。如果不是,则创建一个新的线程执行提交的任务,如果是,则执行饱和策略。
下面是线程池的执行流程:
在Java中实现的线程池的核心类是ThreadPoolExecutor,该类的execute方法的执行流程就是上面的过程。注意上面三个加粗的词汇:核心线程池、工作队列和饱和策略。细化到ThreadPoolExecutor执行execute方法的过程,对上面的过程补充如下:核心线程池对应corePoolSize变量的值,如果运行的线程小于corePoolSize,则创建新的线程执行任务(这个过程需要获取全局锁);如果运行的线程大于corePoolSize,则将任务加入BlockingQueue(对应工作队列);如果无法加入则创建新的线程执行任务,这个步骤中,如果创建新线程后当前运行的线程数大于maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExcution()方法。
ThreadPoolExecutor为了避免执行新提交的任务获取全局锁,ThreadPoolExecutor在创建后会执行一个预热过程,所谓预热就是让当前运行的线程数大于等于corePoolSize。这样,后面新提交的任务都将直接加入到BlockingQueue。而这个过程是不需要获取全局锁的,自然就能提高线程池的性能。为了对ThreadPoolExecutor执行execute方法的过程一探究竟,来扒扒其源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
如果线程池能够创建线程执行任务,那么将调用addWorker方法,将线程池创建的线程封装为Worker,Worker在执行完任务后还会循环获取队列中任务来执行。看看addWorker方法的源码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
之后我们看看执行t.start()后会发生的事,因为Worker本身实现了Runnable,所以start后将调用Worker的run方法,源码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
以上源码其实就干了一件事:创建的线程在执行完提交的任务后会反复从BlockingQueue中获取任务来执行。
使用线程池
前面分析了线程池的执行过程以及对源码进行了剖析,下面我们自己创建一个线程池并熟悉线程池的使用。创建一个线程池需要几个输入参数:
- corePoolSize:线程池的基本大小。当提交一个任务给线程池时,线程池会创建一个线程执行任务,但是即使线程池有空闲的线程也会创建新的线程,直到执行的任务数大于等于corePoolSize时就不再创建。
- runnableTaskQueue:用于保存等待执行的任务的阻塞队列。
- maximumPoolSize:线程池的最大数量。如果线程池的工作队列满了,并且已经创建的线程数小于最大的线程数,那么线程池会再创建新的线程执行任务。
- ThreadFactory:用于设置创建线程的工厂。
- RejectedExecutionHandler:饱和策略。当队列和线程池都满了,说明线程已经处于饱和状态,那么必须采取一种策略处理提交的任务。默认的策略是拒绝执行,也就是抛出异常。
提交任务的方式有两种:execute()和submit()。区别在于后者提交的任务是有返回值的。返回值可以通过Future对象的get()方法得到。执行后需要调用线程池的关闭关闭线程池。主要有shutdown()和shutdownNow()两个方法,原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt()方法中断线程。区别在于shutdownNow()方法首先会把线程池的状态设为STOP,然后尝试终止正在执行的线程和等待执行的线程,并返回等待执行的任务列表;而shutdown()方法只是将线程池的状态设为SHUTDOWN,然后中断所有没有正在执行的线程。一般而言,如果要等到任务执行完再关闭线程池,则调用shutdown()方法,如果不一定要等到把任务执行完,那么就执行shutdownNow()方法。
一个简单的线程池的例子如下(建议造轮子):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
以上程序的执行结果为:
ThreadPool-Worker-1 add to workQueue!
ThreadPool-Worker-2 add to workQueue!
ThreadPool-Worker-3 add to workQueue!
ThreadPool-Worker-4 add to workQueue!
ThreadPool-Worker-5 add to workQueue!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool receives a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-2 get result 19:36:48
ThreadPool-Worker-3 get result 19:36:48
ThreadPool-Worker-4 get result 19:36:48
ThreadPool-Worker-5 get result 19:36:48
work done! Time is 19:37:03
从线程池的实现上可以看到,当客户端调用execute(Runnable task)时,会不断将任务加入工作队列BlockingQueue中,而每个工作者线程DemoThread则不断从工作队列中取任务执行,当工作队列为空时,工作者线程进入等待状态。执行完任务后线程调用shutdown()方法关闭线程池,要注意的是调用了工作者线程的stopToSelf方法停止从工作队列中取任务执行,除了把shutdown变量设为false外,还调用工作者线程的interrupt方法中断线程,进行如此操作的目的是将因为需要取任务而陷入等待的工作者线程进行中断从而让其从wait方法返回,然后停止执行。
可以看到,线程池的本质就是使用了一个线程安全的工作队列连接线程和客户端线程,客户端线程将任务放入工作队列后便直接返回,而工作者线程则不断从工作队列上取出任务并执行。当工作队列为空时,所有的工作者线程都等待在工作队列上,当有客户端提交新的任务时便会通知任意一个工作者线程,随着新提交的任务的增多,将有更多的工作者线程被唤醒。