多线程

ThreadPoolExecutor

线程池

好处:

  1. 降低资源的消耗。线程本身就是一种资源。创建和销毁线程会有CPU开销。创建的线程也会占用一定的内存。

  2. 提高任务执行的响应速度,执行任务时可以不必等到线程创建完之后在执行。

  3. 提高线程的可管理性,线程不能无限的创建,需要进行统一的分配调优监控。

​ 不适用线程池的话,频繁的线程创建和销毁会占用更多的cpu和内存。频繁的线程创建和销毁会对gc产生比较大的压力。线程太多,线程间切换带来的开销将不可忽视,线程太少,多核cpu得不到充分利用,是一种浪费。

​ 可以看出合理使用的线程池是多么重要的事情。

img-0

   先从线程池的构造函数看起(这里找了个最多参数的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(int corePoolSize, // 1
int maximumPoolSize, // 2
long keepAliveTime, // 3
TimeUnit unit, // 4
BlockingQueue<Runnable> workQueue, // 5
ThreadFactory threadFactory, // 6
RejectedExecutionHandler handler ) { //7
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;
}
序号 名称 类型 含义
1 corePoolSize int 线程池里的核心线程数量
2 maximumPoolSize int 线程池里允许有的最大线程数量
3 keepAliveTime long 线程最大空闲等待时间
4 unit TimeUnit 时间单位分时秒
5 workQueue BlockingQueue Runable 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略

  corePoolSize线程池里面的核心线程池,这里面的线程是并行的,并行和并发是两个概念,并发是CPU切换的速度快到人们感受不到所以认为任务是在一起允许。而并行则是真正的在一起执行,这需要我们的CPU拥有多个核心才行。比如我的电脑是8核16线程,则代表我的这台电脑在同一时间可以并行允许16个线程。这里有个Demo可以自己跑起来看下:

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
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
threadPoolDemo.doPricess();
}
//Runtime.getRuntime().availableProcessors() 获取的是你的电脑核心数比如8核16线程
void doPricess(){
ExecutorService executorService = null;
executorService = new ThreadPoolExecutor(16,
16,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<40;i++){
TestPoolThreadRunnable thread = new TestPoolThreadRunnable(i);
executorService.execute(thread);
}
executorService.shutdown();
}
class TestPoolThreadRunnable implements Runnable{
Integer count;
TestPoolThreadRunnable(Integer count){
this.count = count;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count++ +" "+ Thread.currentThread().getId());
}
}
}
// 上面我创建的线程池给的核心数是16,然后每个任务给了个延迟一秒的执行(这样可以更好的观察并行现象)
// 然后我循环创建了40个任务,可以看到当核心线程数给了16那么在一秒钟瞬间有16个线程同时执行,瞬间16个任务一起就完成了。然后你把核心线程数改成2,你会发现一秒钟里面同时创建了两个线程在执行任务,每次都是两个两个在运行。
// 为什么会出现这种现象?从线程池的工作原理可以看出。提交任务后会先判断核心线程数是不是满了没有满的话会创建线程执行任务,知道核心线程满了才会不在创建任务,然后满了后的任务会放到列表LinkedBlockingDeque里面等着。等到核心空下来了在下来了在执行。
// 通过文字要是没看懂的话 可以把上面代码跑起来看下。

触发问题:java.util.concurrent.RejectedExecutionException:这个错误的意思就是往线程池里面提交的任务被拒绝了。那什么情况下会出现线程被线程池拒绝呢?

  1. 当线程等待队列用的是有限的比如SynchronousQueue这个队列,当我们的核心线程满了,然后队列也满了然后运行的最大线程数量也就是(maximumPoolSize)也满了,这个时候就会报RejectedExecutionException奔溃。
  2. 当线程池显示的调用了shutDown方法后,线程池又去提交了任务,这个时候就会报这个错误。
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
// 造成奔溃的例子
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
try {
threadPoolDemo.doPricess();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ExecutorService executorService = null;
//Runtime.getRuntime().availableProcessors() 获取的是你的电脑核心数比如8核16线程
void doPricess() throws InterruptedException {
executorService = new ThreadPoolExecutor(5,
8,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<40;i++){
TestPoolThreadRunnable thread = new TestPoolThreadRunnable(i);
executorService.execute(thread);
// 执行了shutdown然后又提交了任务会触发RejectedExecutionException奔溃
executorService.shutdown();
}
}
class TestPoolThreadRunnable implements Runnable{
Integer count;
TestPoolThreadRunnable(Integer count){
this.count = count;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count++ +" "+ Thread.currentThread().getId()+" "+System.currentTimeMillis());
}
}
}