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

线程池的优点就不说了,本文主要记录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 被处理
发布了42 篇原创文章 · 获赞 7 · 访问量 7751

猜你喜欢

转载自blog.csdn.net/HouXinLin_CSDN/article/details/104211430
今日推荐