线程池的优点就不说了,本文主要记录ThreadPoolExecutor的7个参数用途。
ThreadPoolExecutor有7个参数的构造器,当然另一个5个参数的构造器也是调用这个。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
corePoolSize:指定核心线程数量的大小。
maximumPoolSize:指定线程池中最大线程的数量。
keepAliveTime:线程池中的线程数量超过corePoolSize时,这些空闲线程存活时间,超过了被销毁。
unit:keepAliveTime的单位。
workQueue:任务存放列队,当核心线程中没有空闲的线程处理他时,新提交的任务将存放到这里。
threadFactory:线程创建工厂。这个比较简单,就是自己控制返回Thread,对这个Thread有什么各种图谋不轨的想法可以在这里处理。
handler:线程拒绝策略,也就是被提交的线程数量过多,线程池无法处理,交给这个handler,不管什么也不做也好,还是把被提交的线程存放的一个新的列队后,过段时间重新提交也好,都可以自己实现。
编写一个测试程序,以图形化方式,后续方便测试。
直接新建一个Main2类,复制进去即可。
import sun.misc.Unsafe;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.lang.reflect.Field;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class Main2 extends JFrame {
private static final int CORE_POLL_SIZE = 1;
private static final int MAX_POLL_SIZE = 1;
//线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POLL_SIZE, MAX_POLL_SIZE, 5, TimeUnit.SECONDS,
new MyBlockingQueue<>(), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("都无法处理");
}
});
//存放非核心线程,用来验证keepAliveTime参数
private static Thread mVerificationNotCoreThread = null;
//所有的核心与非核心线程存放
private static List<Thread> mThread = new ArrayList<Thread>() {
@Override
public boolean add(Thread o) {
if (!contains(o)) {
System.out.println("List中添加非核心线程" + o.getName());
super.add(o);
}
return true;
}
};
public Main2() {
this.setSize(500, 500);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setLayout(new FlowLayout());
//打印总任务数量,活动线程数量,当前池内大小
this.add(new MyButton("打印线程数量") {
@Override
public void click(ActionEvent e) {
printThreadCount(new String[]{"TaskCount", "ActiveCount", "PoolSize"},
new long[]{poolExecutor.getTaskCount(), poolExecutor.getActiveCount(), poolExecutor.getPoolSize()});
}
});
//向线程池中添加任务
this.add(new MyButton("添加线程") {
@Override
public void click(ActionEvent e) {
String sizeString = JOptionPane.showInputDialog("创建的线程池");
for (int i = 0; i < Integer.valueOf(sizeString); i++) {
int rand = new Random().nextInt(50) + 50;
poolExecutor.execute(new MyRunnable(poolExecutor, rand));
}
}
});
//当没有活动线程时输出信息
this.add(new MyButton("监听活动线程为0") {
@Override
public void click(ActionEvent e) {
new Thread(() -> {
while (true) {
if (poolExecutor.getActiveCount() == 0) {
System.out.println("活动线程为0");
break;
}
}
}).start();
}
});
//查看List中所有线程状态
this.add(new MyButton("查看所有线程状态") {
@Override
public void click(ActionEvent e) {
for (int i = 0; i < mThread.size(); i++) {
System.out.println(mThread.get(i).getName() + " " + mThread.get(i).isAlive());
}
}
});
//用来验证keepAliveTime参数
this.add(new MyButton("时间验证") {
@Override
public void click(ActionEvent e) {
if (mVerificationNotCoreThread != null) {
System.out.println(mVerificationNotCoreThread.getName() + " " + mVerificationNotCoreThread.isAlive());
} else {
System.out.println("没有待验证线程");
}
}
});
}
private void printThreadCount(String[] name, long[] value) {
for (int i = 0; i < name.length; i++) {
System.out.print(name[i] + "=" + value[i] + " |");
}
System.out.println();
}
static class MyRunnable implements Runnable {
private int i;
private ThreadPoolExecutor mThreadPoolExecutor;
public MyRunnable(ThreadPoolExecutor threadPoolExecutor, int i) {
this.mThreadPoolExecutor = threadPoolExecutor;
this.i = i;
}
@Override
public void run() {
try {
//List中添加线程
mThread.add(Thread.currentThread());
System.out.println("线程运行开始:" + Thread.currentThread().getName() + " 开始==" + i);
//模拟任务处理时间,sleep为(1+2+3+...+i); 最大睡眠5050秒
while (--i > 0) {
Thread.sleep(i);
}
System.out.println("线程运行结束:" + Thread.currentThread().getName() + " 结束==" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//自定义LinkedBlockingQueue
static class MyBlockingQueue<E> extends LinkedBlockingQueue<E> {
public MyBlockingQueue() {
}
public MyBlockingQueue(int capacity) {
super(capacity);
}
public MyBlockingQueue(Collection<? extends E> c) {
super(c);
}
//向列队中添加任务时输出日志
@Override
public boolean offer(E e) {
boolean result = super.offer(e);
System.out.println(Thread.currentThread().getName() + "向列队添加暂存" + e + " " + size());
return result;
}
//向列队中取出任务时输出日志
@Override
public E take() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "线程池取出任务阻塞开始");
E e = super.take();
System.out.println(Thread.currentThread().getName() + "线程池取出任务阻塞结束,取出元素:" + e);
return e;
}
//向列队中取出任务时输出日志
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long startTimer =System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "线程尝试在指定时间内获取任务---start");
E e = super.poll(timeout, unit);
if (e==null){
if (mVerificationNotCoreThread==null){
System.out.println(Thread.currentThread().getName()+"-------------第一个线程poll为空----------------------------");
System.out.println("相差"+((System.currentTimeMillis() - startTimer)) / 1000 +"秒");
mVerificationNotCoreThread=Thread.currentThread();
}
}
System.out.println(Thread.currentThread().getName() + "线程池取出任务:" + e);
return e;
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
this.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
click(e);
}
});
}
public void click(ActionEvent e) {
}
}
public static void main(String[] args) {
new Main2();
while (true) {
}
}
}
各个参数验证
一、验证corePoolSize核心线程数量大小和workQueue存放线程列队。
假设指定了corePoolSize为1,也就是核心工作的线程数量为1,要处理后续被添加的任务也是由这1个线程来完成。并且如果核心线程中没有空闲线程来处理新任务,则新添加的任务被存放到workQueue列队中,只要重写一下offer方法打印就可以看到列队中有没有过被添加的任务。
首先修改线程池参数,核心大小为1
运行程序后,点击添加线程,输入3。
最后从输出中可以看到,只有1个线程在工作,也就是pool-1-thread-1 ,并且打印了向列队添加暂存Main2$MyRunnablexxx任务的信息,pool-1-thread-1执行任务结束后会从列队中获取新任务,再次去执行,意味着取到列队中的信息要调用take()方法,程序重写了take(),并且也做了日志。也可以看到两次添加以及两次取出。
List中添加核心线程pool-1-thread-1
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@2e5b650d 1
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@21844807 2
线程运行开始:pool-1-thread-1 开始==52
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程池取出任务阻塞开始
pool-1-thread-1线程池取出任务阻塞结束,取出元素:Main2$MyRunnable@2e5b650d
线程运行开始:pool-1-thread-1 开始==56
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程池取出任务阻塞开始
pool-1-thread-1线程池取出任务阻塞结束,取出元素:Main2$MyRunnable@21844807
线程运行开始:pool-1-thread-1 开始==88
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程池取出任务阻塞开始
二、验证maximumPoolSize最大线程数量和拒绝策略
当核心线程数中没有空闲线程时,而又有新的任务被添加,则暂时先添加到任务列队中,也就是上面验证的,其次,如果任务列队满了(要手动控制列队的大小),则如果目前创建的所有线程数量没有超过maximumPoolSize,则创建一个新线程执行这个任务,这个新线程也称之为非核心线程,如果超过,则执行拒绝策略。
假设核心数量为2,列队大小为1,maximumPoolSize为4,也就是说,如果核心线程没有空闲时,则先添加到列队,但列队大小仅仅为1,所以当这个列队满的时候,判断所有工作线程是不是小于4个(此时当前线程数量应该为2,也就是2个核心线程已经在运行),小于则创建非核心线程执行这个任务,最大创建的非核心数量为maximumPoolSize-corePoolSize,也就是2个非核心线程。否则则执行拒绝策略。
要想使maximumPoolSize生效,则最少提交的任务数要大于corePoolSize+阻塞列队的大小。
更改程序配置。
运行程序,经过上面的计算,只有输入4和5时才创建非核心线程,超过5则执行拒绝策略。
当输入4时,可能输出如下,可以看到有一个pool-1-thread-3线程,而这个就是非核心线程。并且,列队中仅存放的那个任务会被这3个线程中某一个完成。
从下面输出中发现pool-1-thread-2一开始执行时被随机分配到了56,则这个线程要sleep(1+2+3+…+56)秒,他最先完成,则他先开始从列队中取出任务并执行。也就是第4个任务开始是pool-1-thread-2 来完成(根据自己的输出观察呦),并且最后发现只有2个线程以阻塞式从列队中获取下一个任务,另一个线程被杀掉,也就是ThreadPoolExecutor将保持线程数为corePoolSize大小。
并且最后点击打印线程数量发现,总共执行了4个任务,当前活动线程是0,线程数量为2,也就是池内现在有2个。但如果在3个线程都在运行期间打印,此时就会输出3。最大是也就是maximumPoolSize个。
List中添加核心线程pool-1-thread-1
线程运行开始:pool-1-thread-1 开始==57
List中添加核心线程pool-1-thread-2
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@6e2d1d13 1
线程运行开始:pool-1-thread-2 开始==56
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@708650a4 1
List中添加非核心线程pool-1-thread-3
线程运行开始:pool-1-thread-3 开始==94
线程运行结束:pool-1-thread-2 结束==0
pool-1-thread-2线程尝试在指定时间内获取任务---start
pool-1-thread-2线程池取出任务:Main2$MyRunnable@6e2d1d13
线程运行开始:pool-1-thread-2 开始==89
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-3 结束==0
pool-1-thread-3线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-2 结束==0
pool-1-thread-2线程尝试在指定时间内获取任务---start
pool-1-thread-1线程池取出任务:null
pool-1-thread-3线程池取出任务:null
pool-1-thread-3线程池取出任务阻塞开始
pool-1-thread-2线程池取出任务:null
pool-1-thread-2线程池取出任务阻塞开始
TaskCount=4 |ActiveCount=0 |PoolSize=2 |
TaskCount=4 |ActiveCount=0 |PoolSize=2 |
重启应用输入5,此时多了pool-1-thread-4,也就是有4个线程来处理任务,并在点击打印线程数则输出池内有4个线程,另一个任务被前4个最先处理完的获取并处理。并且最后同样只有2个线程以阻塞方式拿取新任务。
List中添加核心线程pool-1-thread-1
List中添加核心线程pool-1-thread-2
线程运行开始:pool-1-thread-2 开始==89
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@6d21d235 1
线程运行开始:pool-1-thread-1 开始==95
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@4b479820 1
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@27349817 1
List中添加非核心线程pool-1-thread-3
线程运行开始:pool-1-thread-3 开始==54
List中添加非核心线程pool-1-thread-4
线程运行开始:pool-1-thread-4 开始==55
TaskCount=5 |ActiveCount=4 |PoolSize=4 |
线程运行结束:pool-1-thread-3 结束==0
pool-1-thread-3线程尝试在指定时间内获取任务---start
pool-1-thread-3线程池取出任务:Main2$MyRunnable@6d21d235
线程运行开始:pool-1-thread-3 开始==94
线程运行结束:pool-1-thread-4 结束==0
pool-1-thread-4线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-2 结束==0
pool-1-thread-2线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-3 结束==0
pool-1-thread-3线程尝试在指定时间内获取任务---start
pool-1-thread-4线程池取出任务:null
pool-1-thread-2线程池取出任务:null
pool-1-thread-1线程池取出任务:null
pool-1-thread-1线程池取出任务阻塞开始
pool-1-thread-3线程池取出任务:null
pool-1-thread-3线程池取出任务阻塞开始
TaskCount=5 |ActiveCount=0 |PoolSize=2 |
大于5时会有一条都无法处理
输出,也就是执行了拒绝策略。
JDK提供了4中方式
DiscardOldestPolicy:淘汰旧任务。
AbortPolicy:抛异常。
CallerRunsPolicy:用提交任务的线程去处理这个任务。
DiscardPolicy:什么也不做。
而我自定义了一个,只输出都无法处理。
三、验证keepAliveTime
当某个空闲线程尝试poll(keepAliveTime)获取列队任务为空后,则自杀,但是jdk保证了自杀前剩余的线程不能小于核心数量,否则调用take阻塞等待下一个任务。
也就是当某个线程执行玩自己任务后,试图调用poll(keepAliveTime)获取新任务,但是指定时间后为空,则在自旋时发现所有线程数大于核心数,则当前线程退出,也就是退出所有非核心线程,否则表示自己是核心线程,则以take()阻塞式从列队中获取下一个任务。
修改线程配置,尽量小一点,空闲秒数为6秒。
在程序中输入3,也就是说,有一个核心线程在处理,有一个被添加到任务列队,有一个会以非核心线程执行,并且其中一个执行完成之后调用poll(),在给定时间内尝试获取,如果到时间了还为空,那么这个线程因为要保持核心线程数量将选择自杀。当然另一个在poll()为空时,将选择take()阻塞。
只要在poll方法下做一个记录就可以,首先某个线程试图在指定时间内poll后为空时,记录第一个获取为空的线程,利用System.currentTimeMillis结束时间-开始时间/1000得出等待秒数。
List中添加非核心线程pool-1-thread-1
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@5f11fc4c 1
线程运行开始:pool-1-thread-1 开始==83
AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@2e82d8c9 1
List中添加非核心线程pool-1-thread-2
线程运行开始:pool-1-thread-2 开始==86
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程尝试在指定时间内获取任务---start
pool-1-thread-1线程池取出任务:Main2$MyRunnable@5f11fc4c
线程运行开始:pool-1-thread-1 开始==65
线程运行结束:pool-1-thread-2 结束==0
pool-1-thread-2线程尝试在指定时间内获取任务---start
线程运行结束:pool-1-thread-1 结束==0
pool-1-thread-1线程尝试在指定时间内获取任务---start
pool-1-thread-2-------------第一个线程poll为空----------------------------
相差6秒
pool-1-thread-2线程池取出任务:null
pool-1-thread-1线程池取出任务:null
pool-1-thread-1线程池取出任务阻塞开始
pool-1-thread-1 true
pool-1-thread-2 false
其中输出相差6秒,6秒后这个线程被杀,点击窗口中的查看所有线程状态打印出pool-1-thread-2 已经不可用了。
简单实现自定义拒绝策略重新提交
import java.util.concurrent.*;
public class TestThreadPoolExecutor {
private static final int CORE_POLL_SIZE = 1;
private static final int MAX_POLL_SIZE = 2;
private static LinkedBlockingQueue<Runnable> mRunnables = new LinkedBlockingQueue<>();
static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POLL_SIZE, MAX_POLL_SIZE, 6, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
mRunnables.offer(r);
}
});
private static void startRejected() {
for(;;){
try {
Thread.sleep(1000);
poolExecutor.execute(mRunnables.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
poolExecutor.submit(new TestRunnable(i));
}
startRejected();
}
static class TestRunnable implements Runnable{
private int i;
public TestRunnable(int i) {
this.i = i;
}
@Override
public void run() {
try {
Thread.sleep(1200);
System.out.println(i+" 被处理");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
0 被处理
3 被处理
2 被处理
1 被处理
5 被处理
6 被处理
7 被处理
8 被处理
9 被处理
4 被处理