jdk-ThreadPoolExecutor(二)---内部参数和拒绝策略

承继上一篇,上一篇主要分析了下ThreadPoolExecutor中的大概执行逻辑,这一篇主要分析一下内部参数的写一写测试程序。

首先来看里面的最终的默认构造方法,分析其参数,以及其流程

//Executors中的四个构造方法最终调用的是下面一个初始化方法
    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    //1. corePoolSize 核心线程数,所谓核心线程数,就是会一直存活的线程数,即使期间没有需要执行的任务
    //   因此,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理任务,并不是交由现有
    //   的线程处理,allowCoreThreadTimeout(true),默认为false,可以设置核心线程超时关闭
    //2. maximumPoolSize,当线程数>=核心线程数时,根据上一篇的分析,
    //2.1如果此时任务队列没有满,此时会加入到任务队列中,等待任务调度执行,
    //2.2如果此时任务队列已满,此时会判断线程数与maximumPoolSize的大小
    //   2.2.1如果此时线程数没有达到maximumPoolSize,那么就会创建新的线程执行任务
    //   2.2.2如果此时线程数等于maximumPoolSize,那么就标志着超出线程池的处理范围,线程池会拒绝掉,具体
    //       拒绝策略下面再分析
    //3. keepAliveTime,当线程空闲时间达到keepAliveTime时,改线程会退出,直至线程数目为corePoolSize,
    //   如果allowCoreThreadTimeout设置为true的话,则所以线程均会退出直至线程数量为0.

    //可总结下线程池的任务执行策略
    //1.当线程数小于核心线程数时,创建线程
    //2.当线程数等于核心线程数时,且此时任务队列未满,将任务放入任务队列
    //3.当线程数大于等于核心线程数,且任务队列已满
    //  3.1若线程数小于最大线程数,创建线程
    //  3.2若线程数等于最大线程数,拒绝任务


测试AbortPolicy

//测试线程池拒绝策略
public class TestExceptionThread {

    private static int totalNum = 2;

    //ThreadPoolExecutor 提供了四种拒绝策略
    //1.AbortPolicy,顾名思义,直接拒绝掉本次线程,抛出RejectedExecutionException
    //测试该种拒绝策略
    public static void main(String[] args){

        //构建出一个只有3个线程池大小的service
        ExecutorService service = Executors.newFixedThreadPool(totalNum);

        for(int i = 0; i < 5; i++){
            final int j = i;
            System.out.println("线程"+j+"创建");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("线程"+j+"执行中");
                        Thread.sleep(50000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
    }
}
结果如下,并没有抛出异常?奇怪了。。。怎么回事,难不成线程一直在加入,没有执行到抛出异常的范围.
线程0创建
线程1创建
线程0执行中
线程2创建
线程3创建
线程4创建
线程1执行中
问题其实出在初始化步骤,可以看见初始化时使用了LinkedBlockingQueue的默认大小,这样的话,队列大小为Integer.MAX_VALUE,这种情况下,最大线程数就失去作用了,根据上述步骤,此时核心线程数虽然已满,但是队列确很大,因此全部加入了队列任务中去了,最终会被任务调度执行,可见,队列的大小设置也是有讲究的。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
再次测试,这次设置队列任务大小为2,核心线程数为2,最大线程数为3.
//测试线程池拒绝策略
public class TestExceptionThread {

    private static int totalNum = 2;

    //ThreadPoolExecutor 提供了四种拒绝策略
    //1.AbortPolicy,顾名思义,直接拒绝掉本次线程,抛出RejectedExecutionException
    //测试该种拒绝策略
    public static void main(String[] args) throws Exception{

        //构建出一个只有3个线程池大小的service

        LinkedBlockingQueue queue = new LinkedBlockingQueue(2);
        ExecutorService service = new ThreadPoolExecutor(totalNum, totalNum+1, 0, TimeUnit.MILLISECONDS,queue);

        for(int i = 0; i < 10; i++){
            final int j = i;
            Thread.sleep(2000);
            System.out.println("线程"+j+"创建");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("线程"+j+"执行中");
                        Thread.sleep(50000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
    }
}

测试结果如下:来看看和预期对不对.

核心线程数大小为2,最大数为3,队列任务是2.

1.线程0创建时,线程池中没有线程,执行时,线程池中运行线程数目为 1。

2.线程1创建时,核心线程为线程0,只有一个,没有到达核心线程池大小2,因此直接加入执行.此时运行线程数目为 2

3.线程2创建时,核心线程数为线程0,线程1.到达核心线程数2,因此加入到任务队列中去,任务队列中为1。

4.线程3创建时,加入到任务队列中去,因此任务队列为2,核心数还是2。

5.线程4创建时,任务队列已满了。但是此时运行的线程数只有线程0和线程1,小于最大线程数3,因此线程4被创建出并且执行了。(从结果图中可以看出)。

6.线程5创建时,运行线程数是3,并且队列已满,并且等于最大线程数了,所以直接抛出异常,因为此时线程池已经不能处理了.

7.后续的线程此时已经没办法进入了,因为线程5抛出异常了。

线程0创建
线程0执行中
线程1创建
线程1执行中
线程2创建
线程3创建
线程4创建
线程4执行中
线程5创建
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@4b33650d rejected from java.util.concurrent.ThreadPoolExecutor@43095c6c[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110)
	at concurrenttest.TestExceptionThread.main(TestExceptionThread.java:40)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

测试CallerRunsPolicy如下:

public class TestExceptionThread {

    private static int totalNum = 2;

    public static void main(String[] args) throws Exception{

        //构建出一个只有3个线程池大小的service

        LinkedBlockingQueue queue = new LinkedBlockingQueue(2);
        ExecutorService service = new ThreadPoolExecutor(totalNum, totalNum+1, 0,
                TimeUnit.MILLISECONDS,queue,Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());

        for(int i = 0; i < 10; i++){
            final int j = i;
            Thread.sleep(2000);
            System.out.println("线程"+j+"创建");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println(Thread.currentThread().getName()+"线程"+j+"执行中");
                        Thread.sleep(500000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            Thread.sleep(2000);
            service.submit(runnable);
        }

        System.out.println("主线程输出");

    }
}
可以看见,此时线程5在执行失败后,并没有抛出异常,而是由调用线程继续执行,也即是这边的主线程,但是此时主线程后续的输出并没出来,说明这种方式会阻塞主线程。也算是个缺点吧。在这种情况下,感觉并不是很好.
线程0创建
pool-1-thread-1线程0执行中
线程1创建
pool-1-thread-2线程1执行中
线程2创建
线程3创建
线程4创建
pool-1-thread-3线程4执行中
线程5创建
main线程5执行中

测试DiscardPolicy,其实看下源码就会发现这种处理很直接啊,什么方法都没有,这就意味着直接抛弃掉了当前线程的创建,继续执行之后的逻辑。

public class TestExceptionThread {

    private static int totalNum = 2;

    public static void main(String[] args) throws Exception{

        //构建出一个只有3个线程池大小的service

        LinkedBlockingQueue queue = new LinkedBlockingQueue(2);
        ExecutorService service = new ThreadPoolExecutor(totalNum, totalNum+1, 0,
                TimeUnit.MILLISECONDS,queue,Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());

        for(int i = 0; i < 10; i++){
            final int j = i;
            Thread.sleep(2000);
            System.out.println("线程"+j+"创建");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println(Thread.currentThread().getName()+"线程"+j+"执行中");
                        Thread.sleep(500000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            Thread.sleep(2000);
            service.submit(runnable);
        }

        System.out.println("主线程输出");

    }
}

从结果可以看出,线程5,开始就开始抛弃了,最终是回到主线程中输出日志,那么其实如果在线程6-线程9的新建过程中,线程池可以加入了,那么还是可以直接加入的。

线程0创建
pool-1-thread-1线程0执行中
线程1创建
pool-1-thread-2线程1执行中
线程2创建
线程3创建
线程4创建
pool-1-thread-3线程4执行中
线程5创建
线程6创建
线程7创建
线程8创建
线程9创建
主线程输出


测试DiscardOldestPolicy,这个拒绝策略有点意思,它会将队列中添加最早的踢出掉,添加上新的线程进去。感觉这种策略也不是很好啊,毕竟会丢失线程,主要看你是怎么规划线程执行的任务的,感觉如果是执行多线程去执行一批任务,允许线程不执行正确的情况下,可以考虑。
public class TestExceptionThread {

    private static int totalNum = 2;

    public static void main(String[] args) throws Exception{

        //构建出一个只有3个线程池大小的service

        LinkedBlockingQueue queue = new LinkedBlockingQueue(2);
        ThreadPoolExecutor service = new ThreadPoolExecutor(totalNum, totalNum+1, 0,
                TimeUnit.MILLISECONDS,queue,Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());

        for(int i = 0; i < 20; i++){
            final int j = i;
            System.out.println("线程" + j + "创建");
            service.execute(new InnerThread("Thread "+j));
            Iterator iterator = service.getQueue().iterator();
            while (iterator.hasNext()){
                InnerThread thread = (InnerThread) iterator.next();
                System.out.println("队列线程有: "+thread.getName());
            }
        }

        System.out.println("主线程输出");

    }
}

class InnerThread implements Runnable{
    private String name;

    public InnerThread(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try{
            System.out.println("线程" + name + "执行中");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
可以看见大概在线程10到线程19的时候,会出现一直剔除添加的效果。
线程0创建
线程1创建
线程Thread 0执行中
线程2创建
队列线程有: Thread 2
线程3创建
队列线程有: Thread 2
队列线程有: Thread 3
线程4创建
线程Thread 2执行中
线程Thread 3执行中
线程5创建
队列线程有: Thread 5
线程6创建
队列线程有: Thread 5
队列线程有: Thread 6
线程7创建
线程Thread 4执行中
线程Thread 5执行中
线程Thread 6执行中
线程8创建
队列线程有: Thread 8
线程9创建
队列线程有: Thread 8
队列线程有: Thread 9
线程10创建
队列线程有: Thread 8
队列线程有: Thread 9
线程11创建
队列线程有: Thread 9
队列线程有: Thread 11
线程12创建
队列线程有: Thread 11
队列线程有: Thread 12
线程13创建
队列线程有: Thread 12
队列线程有: Thread 13
线程14创建
队列线程有: Thread 13
队列线程有: Thread 14
线程15创建
队列线程有: Thread 14
队列线程有: Thread 15
线程16创建
队列线程有: Thread 15
队列线程有: Thread 16
线程17创建
队列线程有: Thread 16
队列线程有: Thread 17
线程18创建
队列线程有: Thread 17
队列线程有: Thread 18
线程19创建
队列线程有: Thread 18
队列线程有: Thread 19
主线程输出
线程Thread 10执行中
线程Thread 18执行中
线程Thread 19执行中
线程Thread 1执行中
线程Thread 7执行中


当然,从上面可以看出,拒绝策略其实自己可以自定义啊。下面测试一下自定义获取异常
public class TestExceptionThread {

    private static int totalNum = 2;

    public static void main(String[] args) throws Exception{

        LinkedBlockingQueue queue = new LinkedBlockingQueue(2);
        ThreadPoolExecutor service = new ThreadPoolExecutor(totalNum, totalNum+1, 0, TimeUnit.MILLISECONDS,queue,new MyReject());

        for(int i = 0; i < 10; i++){
            final int j = i;
            Thread.sleep(2000);
            System.out.println("线程"+j+"创建");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("线程"+j+"执行中");
                        Thread.sleep(50000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            Thread.sleep(2000);
            service.execute(runnable);
        }
    }

    static class MyReject implements RejectedExecutionHandler{
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("你被拒绝了");
        }
    }

}
可以看出这种方式其实比较好,可以由自己决定后续执行逻辑。

线程0创建
线程0执行中
线程1创建
线程1执行中
线程2创建
线程3创建
线程4创建
线程4执行中
线程5创建
你被拒绝了
线程6创建
你被拒绝了
线程7创建
你被拒绝了

本文总结了一下内部一写具体参数的含义,以及线程池的执行策略和拒绝策略。下一篇继续,去看看内部一写方法吧,感觉线程池需要理解的东西比较多,博客还是细分一下,将来自己回头来看时,能瞧的清楚一点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值