线程池

首先传统的三种创建多线程的方式

继承Thread
实现Runnable接口
实现callable接口

代码如下:

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
package ThreadPool;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class Thread01 extends Thread{
@Override
public void run() {
System.out.println("继承Thread");
}

}
class Thread02 implements Runnable{

public void run() {
System.out.println("实现Runnable接口");
}

}
class Thread03 implements Callable<String>{

public String call() throws Exception {
return "实现callable接口";
}

}
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
new Thread01().start();//继承Thread
new Thread(new Thread02()).start();//实现Runnable接口
FutureTask futureTask = new FutureTask(new Thread03());//实现callable接口
new Thread(futureTask).start();
System.out.println(futureTask.get());
}

}

控制台显示:

image-20200219140115077

那么到底什么是线程池:

创建线程时要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程有限,为了避免这些问题,在程序启动时就创建若干线程来响应处理,它们被称为线程池,里面的线程称为工作线程,从jdk1.5开始,JavaAPI提供了EXecutor框架让你可以创建不同的线程池。

话不多说上代码:

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
package ThreadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ThreadPkTest {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
final List<Integer> list=new ArrayList<Integer>();
final Random random =new Random();
for(int i=0;i<10000;i++){
Thread td=new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
list.add(random.nextInt());
}
};
td.start();
td.join();//让调用该方法的线程执行完run()方法后,再执行join后的代码
}
System.out.println("时间:"+(System.currentTimeMillis()-start));
System.out.println("size:"+list.size());
}

}

上面那段代码是没用线程池实现将随机数放在ArrayList中,最后输出所用的时间和ArrayList的大小(size)如下图:

image-20200219141940160

我们此时再将用了线程池的代码进行比对,代码如下:

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
package ThreadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
final List<Integer> list=new ArrayList<Integer>();
ExecutorService executorService=Executors.newSingleThreadExecutor();
final Random random =new Random();
for(int i=0;i<10000;i++){
executorService.execute(new Runnable() {

public void run() {
// TODO Auto-generated method stub
list.add(random.nextInt());
}
});


}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("时间:"+(System.currentTimeMillis()-start));
System.out.println("size:"+list.size());
}

}

控制台输出信息:

image-20200219142614107

可以看出时间短了很多(当然如果电脑处理器好的话就几十毫秒)

线程池的优点

1、避免线程创建和销毁带来的开销

2、避免大量线程间因为互相抢占系统资源导致的阻塞现象

3、能对线程进行简单的管理并提供定时执行、间隔执行等功能

接下来正常普通线程池的创建:

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
package ThreadPool;

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

public class ThreadPool01 {
public static void main(String[] args) {
ExecutorService executorService=Executors.newCachedThreadPool();
executorService.submit(new Runnable(){

public void run() {
// TODO Auto-generated method stub
System.out.println("111");
}

});
executorService.execute(new Runnable(){

public void run() {
// TODO Auto-generated method stub
System.out.println("111");
}

});

executorService.shutdown();
}

}

代码中分别用了submit和execute来输出111,那么两者有什么区别呢?

  1. execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。
  2. submit返回一个Future对象,如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。

线程池核心类

在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。

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

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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
  • corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小。
  • maximumPoolSize:最大线程池大小。
  • keepAliveTime:空余线程存活时间,指的是超过corePoolSize的空余线程达到多长时间才进行销毁。
  • unit:销毁时间单位。
  • workQueue:存储等待执行线程的工作队列。
  • threadFactory:创建线程的工厂,一般用默认即可。
  • handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。

线程池工作流程

1、如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务。
2、如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
3、如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。

一般流程图如下(要详细了解各类线程池流程图的可移步到 https://www.cnblogs.com/linguanh/p/8000063.html ):
一般流程图

线程池分类

Executors是jdk里面提供的创建线程池的工厂类,它默认提供了4种常用的线程池应用,而不必我们去重复构造。

newFixedThreadPool
固定线程池,核心线程数和最大线程数固定相等,而空闲存活时间为0毫秒,说明此参数也无意义,工作队列为最大为Integer.MAX_VALUE大小的阻塞队列。当执行任务时,如果线程都很忙,就会丢到工作队列等有空闲线程时再执行,队列满就执行默认的拒绝策略。

newCachedThreadPool
带缓冲线程池,从构造看核心线程数为0,最大线程数为Integer最大值大小,超过0个的空闲线程在60秒后销毁,SynchronousQueue这是一个直接提交的队列,意味着每个新任务都会有线程来执行,如果线程池有可用线程则执行任务,没有的话就创建一个来执行,线程池中的线程数不确定,一般建议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。

newSingleThreadExecutor
单线程线程池,核心线程数和最大线程数均为1,空闲线程存活0毫秒同样无意思,意味着每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保证了线程的顺序执行。

newScheduledThreadPool
调度线程池,即按一定的周期执行任务,即定时任务,对ThreadPoolExecutor进行了包装而已。

拒绝策略

  • AbortPolicy
    简单粗暴,直接抛出拒绝异常,这也是默认的拒绝策略。

  • CallerRunsPolicy
    如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。

  • DiscardPolicy
    从方法看没做任务操作,即表示不处理新任务,即丢弃。

  • DiscardOldestPolicy
    抛弃最老的任务,就是从队列取出最老的任务然后放入新的任务进行执行。

    如何关闭线程池

1
es.shutdown();

不再接受新的任务,之前提交的任务等执行结束再关闭线程池。

1
es.shutdownNow();

不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list列表

-------------本文结束感谢您的阅读-------------
0%