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

来源:互联网 发布:复杂网络可视化软件 编辑:程序博客网 时间:2024/06/11 15:21

承继上一篇,上一篇主要分析了下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 0