我们简单延用我的多线程博客中实现的callable接口的类对象,来对线程池进行测试。线程池使用Executors工厂类创建有4种实现方法,分别是:newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool和newCachedThreadPool。我们就在下文中逐一使用和分析。
目录
1,Executors.newFixedThreadPool()
(2.1)使用Exectors工厂类的newFixedThreadPool
(2.2)使用ThreadPoolExecutor类来直接创建定长线程池
2,Executors.newSingleThreadExecutor()
(2)newSingleThreadExecutor实现方式的分析
3,Executors.newScheduledThreadPool()
(2)newScheduledThreadPool的内部分析
4,Executors.newCachedThreadPool()
0,简单介绍:
首先对这几种做一个简单介绍:
线程池 | 特点 |
Executors.newFixedThreadPool() | 创建一个灵活的线程池,直接定义线程池的核心线程数和最大线程数,二者相同,控制可并发的线程,没有空闲线程,就将任务放入阻塞队列中等待。 |
Executors.newSingleThreadExecutor() | 线程池中只有一个待使用的线程,线程池的核心线程数和最大线程数都是1,要求单例进行操作需按指定顺序的任务可以使用。 |
Executors.newScheduledThreadPool() | 创建一个延迟和可定期的线程池,用于执行延迟任务和定时任务,他的最大线程数设置为INTERGER.MAX_VALUE,当阻塞队列长度+核心线程数<任务数,就会创建新的线程。当超出核心线程数部分的线程空闲时,就立刻释放了。 |
Executors.newCachedThreadPool() | 创建一个可缓存的线程,他没有核心线程数,只有一个最大线程数,并且设置一个存活时间,当这个线程空闲超过了存活时间就可以回收,但是一直创建,内存遭不住。 |
1,Executors.newFixedThreadPool()
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
(1) 代码实现方法
我们使用两种,一种是Executors工厂类直接使用,一种是使用ThreadPoolExecutor进行实现
package com.lzm.multithreading;
import java.util.Date;
import java.util.concurrent.*;
public class MultithreadingApplication implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是线程:" + Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
//(1)newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
System.out.println("newFixedThreadPool线程池的使用:");
ExecutorService threadPool1 = Executors.newFixedThreadPool(3);
Future<String> threadPool1_1 = threadPool1.submit(abc1);
Future<String> threadPool1_2 = threadPool1.submit(abc1);
Future<String> threadPool1_3 = threadPool1.submit(abc1);
Future<String> threadPool1_4 = threadPool1.submit(abc1);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool1.shutdownNow();
System.out.println("ThreadPoolExecutor线程池的使用:");
LinkedBlockingQueue listQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService exector1 = new ThreadPoolExecutor(4, 5,
1, TimeUnit.MILLISECONDS,
listQueue);
Future<String> threadPool2_1 = exector1.submit(abc1);
Future<String> threadPool2_2 = exector1.submit(abc1);
Future<String> threadPool2_3 = exector1.submit(abc1);
Future<String> threadPool2_4 = exector1.submit(abc1);
Future<String> threadPool2_5 = exector1.submit(abc1);
Future<String> threadPool2_6 = exector1.submit(abc1);
System.out.println(listQueue.size());
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
exector1.shutdownNow();
}
}
运行结果:
(2)两种实现方式的分析
(2.1)使用Exectors工厂类的newFixedThreadPool
使用工厂类的方式帮我们创建相应的对象和方法可以降低代码的耦合性和程序员的工作量。首先我们可以查看一下newFixedThreadPool()的源码内容。如下图,实际上还是通过调用ThreadPoolExecutor类来进行线程池的构建,通过传入一个nThreads常量,来控制ThreadPoolExecutor构造方法中的corePollSize和maximumPoolSize参数。根据运行结果来看,我们通过设置线程数,可以看到核心线程数为3个,所以如果此时需要执行的任务>3,则会被放入阻塞队列,等待空闲线程执行。
这时可能还有疑问:如果需要执行的任务太多了,远大于核心线程数,那么我们该怎么办?我们可以看它下层调用ThreadPoolExecutor类中的参数,看(2.2)节。
(2.2)使用ThreadPoolExecutor类来直接创建定长线程池
实际上线程池的底层类对象就是ThreadPoolExecutor类进行不同线程池的构建,我们首先看一下定长线程池的构建。首先看一下源码和注释,如下图。可以看出主要包含的就是五个参数,我们先解释一下含义,再说明使用方式。
- corePollSize:核心线程数,也就是在正常负载情况下,主要使用这几个线程
- maximumPoolSize:最大线程数,这个线程池可以使用的最大线程数
- keepAliveTime:存活时间,超出核心线程部分的线程,在空闲时的存活时间。
- unit:keepAliveTime的参数,存活时间的量级。
- workQueue:阻塞队列,没有可用线程就放入阻塞队列。
接下来,我们说一下ThreadPoolExecutor类中各自的使用方式,其实corePollSize,很好理解,从我们使用ThreadPoolExecutor创建线程池中,结果可以看出,我们设置为4,则线程池中正常使用的线程就是1,2,3,4号线程。
其次,因为我们设置LinkedBlockingQueue的阻塞队列没有设置容量,所以为默认值最大,我们给了6个任务,阻塞了2个,它的线程使用在1~4之内,说明负载正常。什么时候使用maximumPoolSize呢?我们可以简单做一下测试,设置LinkedBlockingQueue的容量,设置容量为2,6个任务还是使用4个核心线程来执行。如下图:
那我们增加一个任务,此时为7个任务, 结果如下图,我们可以看出阻塞的队列数减小了,这是为什么呢?我们再看线程多了pool-2-thread-5线程,意味着创建了新的线程,而此时已经超过了核心线程数,达到了最大线程数。所以maximumPoolSize的使用是为了在,阻塞容量+核心线程数<任务数 时使用的。
经常上述实验,我们知道了maximumPoolSize是在何时使用的,那么相比较corePollSize,要多出来新创建的线程,那么这些线程怎么办呢?这就要用到keepAliveTime,也就是这些多余的空闲线程的存活时间,当任务压力没这么大了,这些新创建的线程空闲了,等超过存活时间,就回收了。
unit用于设置keepAliveTime的量级,如:TimeUnit.MILLISECONDS按秒。
workQueue:就是阻塞队列的,超过使用线程的任务,我们就放在阻塞队列中等待调用。我们在创建newFixedThreadPool线程池中使用的时LinkedBlockingQueue,都是实现了BlockingQueue接口。
2,Executors.newSingleThreadExecutor()
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
(1)代码实现方法
我们使用两种,一种是Executors工厂类直接使用,一种是使用ThreadPoolExecutor进行实现
package com.lzm.multithreading;
import java.util.Date;
import java.util.concurrent.*;
public class MultithreadingApplication implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是线程:" + Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
System.out.println("newSingleThreadExecutor线程池的使用:");
//(2)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
Future<String> threadPool2_1 = threadPool2.submit(abc1);
Future<String> threadPool2_2 = threadPool2.submit(abc1);
Future<String> threadPool2_3 = threadPool2.submit(abc1);
Future<String> threadPool2_4 = threadPool2.submit(abc1);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool2.shutdownNow();
System.out.println("ThreadPoolExecutor线程池的使用");
LinkedBlockingQueue listQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService exector1 = new ThreadPoolExecutor(1, 1,
1, TimeUnit.MILLISECONDS,
listQueue);
Future<String> threadPool3_1 = exector1.submit(abc1);
Future<String> threadPool3_2 = exector1.submit(abc1);
Future<String> threadPool3_3 = exector1.submit(abc1);
Future<String> threadPool3_4 = exector1.submit(abc1);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
exector1.shutdownNow();
}
}
运行结果:
(2)newSingleThreadExecutor实现方式的分析
我们在上一章节的(2.2)小节进行了ThreadPoolExecutor分析,接下来就不再进行细说。
我们看一下newSingleThreadExecutor()的实现源码,如下图。它实际上就是将核心线程数和最大线程数设置为1,保证只是调用一个线程来执行任务。
3,Executors.newScheduledThreadPool()
newScheduledThreadPool:创建一个可用于周期使用的线程池,用于延迟和定期任务。
(1)代码的实现
package com.lzm.multithreading;
import java.util.Date;
import java.util.concurrent.*;
public class MultithreadingApplication implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是线程:" + Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
System.out.println("newScheduledThreadPool线程池的使用:");
//(3)newScheduledThreadPool 创建一个可用于周期使用的线程池,用于延迟和定期线程。
ScheduledExecutorService threadPool3 = Executors.newScheduledThreadPool(3);
FutureTask<String> f1 = new FutureTask<>(abc1);
FutureTask<String> f2 = new FutureTask<>(abc1);
FutureTask<String> f3 = new FutureTask<>(abc1);
FutureTask<String> f4 = new FutureTask<>(abc1); //超过了核心线程的数量,等待一个线程执行完,在调用他,不存在复用,可以跟上面比较
Date d1 = new Date();
threadPool3.schedule(f1,3,TimeUnit.SECONDS);
threadPool3.schedule(f2,3,TimeUnit.SECONDS);
threadPool3.schedule(f3,3,TimeUnit.SECONDS);
threadPool3.schedule(f4,3,TimeUnit.SECONDS);
try {
String rst = f1.get();
System.out.println(rst);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
threadPool3.shutdownNow();
Date d2 = new Date();
System.out.println("该线程执行完耗时:" + String.valueOf(d2.getSeconds() - d1.getSeconds()) + "s");
}
}
运行结果:
(2)newScheduledThreadPool的内部分析
我们从上述结果可以看出,使用核心线程数运行任务,并且按设置的延迟3s完成了任务。
这次没有用ThreadPoolExecutor进行创建,是因为它内部有一个静态的延迟队列内部类,出了这个包无法调用,所以只用了newScheduledThreadPool()方法进行实现。但是其底层还是用ThreadPoolExecutor进行了线程池的创建,我们从源码来看,首先看一下newScheduledThreadPool的源码,如下图,他只定义了一个核心线程数,然后就调用了该方法的实现 ScheduledThreadPoolExecutor()。
然后我们看一下,ScheduledThreadPoolExecutor()的实现,如第一幅图 ,他是ThreadPoolExecutor的子类,第二幅图,他调用父类的构造器然后将所有的信息传入,也就是调用ThreadPoolExecutor的构造器,最大线程数设为int的最大,并且使用该类中构建的DelayedWorkQueue类,作为阻塞存储队列。
与上述内容不同的是,newScheduledThreadPool()定义的类型是ScheduledExecutorService,所以意味着他的线程执行不是ExecutorService.submit()方法,而是专属的延迟调用方法,ScheduledExecutorService.schedule(Runable/Callable,Time,TimeUnit.SECONDS);,我们看一下这个方法的实现,在ScheduledThreadPoolExecutor类中实现,如下图。我将两个主要调用实现放在一起,我们分析一下:schedule中,将延迟等数据都封装到RunnableScheduledFuture对象中,然后再delayedExecute()中待到时间执行任务。
上述内容实现的是延迟执行,那么定时呢?实际上就是执行scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),每次经过period时间,执行任务。
4,Executors.newCachedThreadPool()
newCachedThreadPool: 创建一个创建一个可缓存线程池,回收空闲线程,并且执行时使用空闲线程,如果没有就创建新线程,如果最大线程为MAX_VALUE那就等着OOM吧。
(1)代码的实现
我懒得用ThreadPoolExecutor,和其他部分的实现相同,所以这部分就简单介绍一下。
package com.lzm.multithreading;
import java.util.Date;
import java.util.concurrent.*;
public class MultithreadingApplication implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是线程:" + Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) {
MultithreadingApplication abc1 = new MultithreadingApplication();
System.out.println("newCachedThreadPool线程池的使用:");
//(4)newCachedThreadPool 创建一个创建一个可缓存线程池,回收空闲线程,并且执行时使用空闲线程,如果没有就创建新线程,如果最大线程为MAX_VALUE那就等着OOM吧。
ExecutorService threadPool4 = Executors.newCachedThreadPool();
Future<String> threadPool4_1 = threadPool4.submit(abc1);
Future<String> threadPool4_2 = threadPool4.submit(abc1);
threadPool4.shutdownNow();
}
}
运行结果:
(2) newCachedThreadPool()的源码
newCachedThreadPool底层还是调用ThreadPoolExecutor来创建线程池,所以我们来看一下源码,如下图。他创建为可缓存的线程池,就是没有核心线程,每次来任务了,没有新的线程就去创建新的线程来执行,这些线程全部都是有存活时间的,与其他线程池不同,其他线程池超出核心线程的部分有存活时间。并且使用同步队列对阻塞任务进行存储。
所以这个线程池不建议使用,他一直创建,一直创建,不要钱的啊!如果一直不释放线程的化,内存让他搞完了,一会就OOM了。