线程池的优点就不说了,本文主要记录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;
publicclassMain2extendsJFrame{privatestaticfinalint CORE_POLL_SIZE = 1;privatestaticfinalint MAX_POLL_SIZE = 2;//线程池ThreadPoolExecutor poolExecutor = newThreadPoolExecutor(CORE_POLL_SIZE, MAX_POLL_SIZE, 6, TimeUnit.SECONDS,newMyBlockingQueue<>(1), Executors.defaultThreadFactory(), newRejectedExecutionHandler() {@Overridepublicvoid rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("都无法处理");}});//存放非核心线程,用来验证keepAliveTime参数privatestaticThread mVerificationNotCoreThread = null;//所有的核心与非核心线程存放privatestaticList<Thread> mThread = newArrayList<Thread>() {@Overridepublicboolean add(Thread o) {if(!contains(o)) {System.out.println("List中添加非核心线程"+ o.getName());super.add(o);}returntrue;}};

publicMain2() {this.setSize(500, 500);this.setVisible(true);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);this.setLayout(newFlowLayout());//打印总任务数量,活动线程数量,当前池内大小this.add(newMyButton("打印线程数量") {@Overridepublicvoid click(ActionEvent e) {                printThreadCount(newString[]{"TaskCount", "ActiveCount", "PoolSize"},newlong[]{poolExecutor.getTaskCount(), poolExecutor.getActiveCount(), poolExecutor.getPoolSize()});}});

//向线程池中添加任务this.add(newMyButton("添加线程") {@Overridepublicvoid click(ActionEvent e) {String sizeString = JOptionPane.showInputDialog("创建的线程池");for(int i = 0; i < Integer.valueOf(sizeString); i++) {int rand = newRandom().nextInt(50) + 50;                    poolExecutor.execute(newMyRunnable(poolExecutor, rand));}}});//当没有活动线程时输出信息this.add(newMyButton("监听活动线程为0") {@Overridepublicvoid click(ActionEvent e) {newThread(() -> {while(true) {if(poolExecutor.getActiveCount() == 0) {System.out.println("活动线程为0");break;}}}).start();}});
//查看List中所有线程状态this.add(newMyButton("查看所有线程状态") {@Overridepublicvoid 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(newMyButton("时间验证") {@Overridepublicvoid click(ActionEvent e) {if(mVerificationNotCoreThread != null) {System.out.println(mVerificationNotCoreThread.getName() + " "+ mVerificationNotCoreThread.isAlive());} else{System.out.println("没有待验证线程");}}});}
privatevoid printThreadCount(String[] name, long[] value) {for(int i = 0; i < name.length; i++) {System.out.print(name[i] + "="+ value[i] + "   |");}System.out.println();
}
staticclassMyRunnableimplementsRunnable{privateint i;privateThreadPoolExecutor mThreadPoolExecutor;
publicMyRunnable(ThreadPoolExecutor threadPoolExecutor, int i) {this.mThreadPoolExecutor = threadPoolExecutor;this.i = i;}
@Overridepublicvoid 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();}
}}
//自定义LinkedBlockingQueuestaticclassMyBlockingQueue<E> extendsLinkedBlockingQueue<E> {publicMyBlockingQueue() {}
publicMyBlockingQueue(int capacity) {super(capacity);}
publicMyBlockingQueue(Collection<? extends E> c) {super(c);}
//向列队中添加任务时输出日志@Overridepublicboolean offer(E e) {boolean result = super.offer(e);System.out.println(Thread.currentThread().getName() + "向列队添加暂存"+ e + "   "+ size());return result;}
//向列队中取出任务时输出日志@Overridepublic E take() throwsInterruptedException{System.out.println(Thread.currentThread().getName() + "线程池取出任务阻塞开始");            E e = super.take();System.out.println(Thread.currentThread().getName() + "线程池取出任务阻塞结束,取出元素:"+ e);return e;}
//向列队中取出任务时输出日志@Overridepublic E poll(long timeout, TimeUnit unit) throwsInterruptedException{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;}
}
classMyButtonextendsJButton{publicMyButton(String text) {super(text);this.addActionListener(newAbstractAction() {@Overridepublicvoid actionPerformed(ActionEvent e) {                    click(e);}});}
publicvoid click(ActionEvent e) {}
}
publicstaticvoid main(String[] args) {newMain2();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-1AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@2e5b650d1AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@218448072线程运行开始:pool-1-thread-1开始==52线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程池取出任务阻塞开始pool-1-thread-1线程池取出任务阻塞结束,取出元素:Main2$MyRunnable@2e5b650d线程运行开始:pool-1-thread-1开始==56线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程池取出任务阻塞开始pool-1-thread-1线程池取出任务阻塞结束,取出元素:Main2$MyRunnable@21844807线程运行开始:pool-1-thread-1开始==88线程运行结束:pool-1-thread-1结束==0pool-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开始==57List中添加核心线程pool-1-thread-2AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@6e2d1d131线程运行开始:pool-1-thread-2开始==56AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@708650a41List中添加非核心线程pool-1-thread-3线程运行开始:pool-1-thread-3开始==94线程运行结束:pool-1-thread-2结束==0pool-1-thread-2线程尝试在指定时间内获取任务---startpool-1-thread-2线程池取出任务:Main2$MyRunnable@6e2d1d13线程运行开始:pool-1-thread-2开始==89线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-3结束==0pool-1-thread-3线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-2结束==0pool-1-thread-2线程尝试在指定时间内获取任务---startpool-1-thread-1线程池取出任务:nullpool-1-thread-3线程池取出任务:nullpool-1-thread-3线程池取出任务阻塞开始pool-1-thread-2线程池取出任务:nullpool-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-1List中添加核心线程pool-1-thread-2线程运行开始:pool-1-thread-2开始==89AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@6d21d2351线程运行开始:pool-1-thread-1开始==95AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@4b4798201AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@273498171List中添加非核心线程pool-1-thread-3线程运行开始:pool-1-thread-3开始==54List中添加非核心线程pool-1-thread-4线程运行开始:pool-1-thread-4开始==55TaskCount=5|ActiveCount=4|PoolSize=4|线程运行结束:pool-1-thread-3结束==0pool-1-thread-3线程尝试在指定时间内获取任务---startpool-1-thread-3线程池取出任务:Main2$MyRunnable@6d21d235线程运行开始:pool-1-thread-3开始==94线程运行结束:pool-1-thread-4结束==0pool-1-thread-4线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-2结束==0pool-1-thread-2线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-3结束==0pool-1-thread-3线程尝试在指定时间内获取任务---startpool-1-thread-4线程池取出任务:nullpool-1-thread-2线程池取出任务:nullpool-1-thread-1线程池取出任务:nullpool-1-thread-1线程池取出任务阻塞开始pool-1-thread-3线程池取出任务:nullpool-1-thread-3线程池取出任务阻塞开始TaskCount=5|ActiveCount=0|PoolSize=2|

大于5时会有一条都无法处理输出,也就是执行了拒绝策略。JDK提供了4种方式。

以JFrame窗口方式详解ThreadPoolExecutor线程池各个参数用途_java

DiscardOldestPolicy:淘汰旧任务。


AbortPolicy:抛异常。


CallerRunsPolicy:用提交任务的线程去处理这个任务。


DiscardPolicy:什么也不做。而我自定义了一个,只输出都无法处理。

以JFrame窗口方式详解ThreadPoolExecutor线程池各个参数用途_java_02

三、验证keepAliveTime

当某个空闲线程尝试poll(keepAliveTime)获取列队任务为空后,则自杀,但是jdk保证了自杀前剩余的线程不能小于核心数量,否则调用take阻塞等待下一个任务。

也就是当某个线程执行玩自己任务后,试图调用poll(keepAliveTime)获取新任务,但是指定时间后为空,则在自旋时发现所有线程数大于核心数,则当前线程退出,也就是退出所有非核心线程,否则表示自己是核心线程,则以take()阻塞式从列队中获取下一个任务。

修改线程配置,尽量小一点好观察,空闲秒数为6秒。

以JFrame窗口方式详解ThreadPoolExecutor线程池各个参数用途_java_03

在程序中输入3,也就是说,有一个核心线程在处理,有一个被添加到任务列队,有一个会以非核心线程执行,并且其中一个执行完成之后调用poll(),在给定时间内尝试获取,如果到时间了还为空,那么这个线程因为要保持核心线程数量将选择自杀。当然另一个在poll()为空时,将选择take()阻塞。


只要在poll方法下做一个记录就可以,首先某个线程试图在指定时间内poll后为空时,记录第一个获取为空的线程,利用System.currentTimeMillis结束时间-开始时间/1000得出等待秒数。

以JFrame窗口方式详解ThreadPoolExecutor线程池各个参数用途_java_04

List中添加非核心线程pool-1-thread-1AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@5f11fc4c1线程运行开始:pool-1-thread-1开始==83AWT-EventQueue-0向列队添加暂存Main2$MyRunnable@2e82d8c91List中添加非核心线程pool-1-thread-2线程运行开始:pool-1-thread-2开始==86线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程尝试在指定时间内获取任务---startpool-1-thread-1线程池取出任务:Main2$MyRunnable@5f11fc4c线程运行开始:pool-1-thread-1开始==65线程运行结束:pool-1-thread-2结束==0pool-1-thread-2线程尝试在指定时间内获取任务---start线程运行结束:pool-1-thread-1结束==0pool-1-thread-1线程尝试在指定时间内获取任务---startpool-1-thread-2-------------第一个线程poll为空----------------------------相差6pool-1-thread-2线程池取出任务:nullpool-1-thread-1线程池取出任务:nullpool-1-thread-1线程池取出任务阻塞开始pool-1-thread-1truepool-1-thread-2false

其中输出相差6秒,6秒后这个线程被杀,点击窗口中的查看所有线程状态打印出pool-1-thread-2 已经不可用了。

简单实现自定义拒绝策略重新提

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被处理