线程池 ThreadPoolExecutor使用详解

ThreadPoolExecutor的构造函数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

中间的数据验证我就删掉了,主要是这几个参数,corePoolSize为线程池创建时的初始容量,类似于一个数组的大小,maximumPoolSize线程池允许的最大容量,workQueue工作队列,当线程池的空闲的线程数量超过corePoolSize时设定多长时间销毁,threadFactory用于创建线程,ThreadPoolExecutor采用的是默认的方式Executors.defaultThreadFactory()handler拒绝策略,就是如果线程总数大于maximumPoolSize时的拒绝策略

workQueue汇总

队列分为直接提交队列,有界任务队列,无界任务队列,任务有限队列

直接提交队列 SynchronousQueue

线上代码

package com.example.demo;


import java.util.concurrent.*;

public class ThreadPool {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        threadService = new ThreadPoolExecutor(1, 8, 1000, TimeUnit.SECONDS, 
                new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 10; i++) {
            threadService.execute(new ThreadTask());
        }
    }
}

class ThreadTask implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

创建一个多线程,线程池最大8个线程,然后加入直接提交队列,我们看一下结果

pool-1-thread-1
pool-1-thread-7
pool-1-thread-6
pool-1-thread-5
pool-1-thread-8
pool-1-thread-4
pool-1-thread-2
pool-1-thread-3
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.example.demo.ThreadTaskTest@27bc2616 rejected from java.util.concurrent.ThreadPoolExecutor@3941a79c[Running, pool size = 8, active threads = 0, queued tasks = 0, completed tasks = 8]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.example.demo.TestThreadPool.main(TestThreadPool.java:14)

可以看到我们执行了8个线程的时候,就执行了拒绝策略。而且最大线程ID就是8,所以可以看到他是直接自行,来一个执行一个,所以用这个队列的时候就要注意最大线程数了,太大的话,会吃爆内存,太小的话任务总是执行不完,正确估计才行

有界任务队列 ArrayBlockingQueue

还是直接看代码

package com.example.demo;


import java.util.concurrent.*;

public class ThreadPool {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        ArrayBlockingQueue<Runnable> runnables = new ArrayBlockingQueue<>(5);
        threadService = new ThreadPoolExecutor(1,2, 1000, TimeUnit.SECONDS,
                runnables ,Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 30; i++) {
            threadService.execute(new ThreadTask(runnables));
        }
    }
}

class ThreadTask implements Runnable {

    ArrayBlockingQueue<Runnable> runnables;

    public ThreadTask(ArrayBlockingQueue<Runnable> runnables) {
        this.runnables = runnables;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前队列线程数为 ->"+ runnables.size() + "线程名称为 ->" + Thread.currentThread().getName());
    }
}

直接结果为

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.example.demo.ThreadTask@12edcd21 rejected from java.util.concurrent.ThreadPoolExecutor@34c45dca[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.example.demo.ThreadPool.main(ThreadPool.java:19)
当前队列线程数为 ->5线程名称为 ->pool-1-thread-1
当前队列线程数为 ->5线程名称为 ->pool-1-thread-2
当前队列线程数为 ->3线程名称为 ->pool-1-thread-2
当前队列线程数为 ->3线程名称为 ->pool-1-thread-1
当前队列线程数为 ->1线程名称为 ->pool-1-thread-2
当前队列线程数为 ->1线程名称为 ->pool-1-thread-1
当前队列线程数为 ->0线程名称为 ->pool-1-thread-2

可以看到一共有7个线程,这个有界队列是先创建corePoolSize大小的线程数量,然后多余的会放到队列中,如果队列满了,则继续创建线程,如果超过了maxPoolSize大小,则执行拒绝策略,。所以这个有界队列如果非常的大,还是会撑爆内存,这点要注意了。
###无界的任务队列 LinkedBlockingQueue
先看代码:

package com.example.demo;


import java.util.concurrent.*;

public class ThreadPool {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        LinkedBlockingQueue<Runnable> runnables = new LinkedBlockingQueue<Runnable>();
        threadService = new ThreadPoolExecutor(1,2, 1000, TimeUnit.SECONDS,
                runnables ,Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 30; i++) {
            threadService.execute(new ThreadTask(runnables));
        }
    }
}

class ThreadTask implements Runnable {

    LinkedBlockingQueue<Runnable> runnables;

    public ThreadTask(LinkedBlockingQueue<Runnable> runnables) {
        this.runnables = runnables;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前队列线程数为 ->"+ runnables.size() + "线程名称为 ->" + Thread.currentThread().getName());
    }
}

执行结果

当前队列线程数为 ->9线程名称为 ->pool-1-thread-1
当前队列线程数为 ->8线程名称为 ->pool-1-thread-1
当前队列线程数为 ->7线程名称为 ->pool-1-thread-1
当前队列线程数为 ->6线程名称为 ->pool-1-thread-1
当前队列线程数为 ->5线程名称为 ->pool-1-thread-1
当前队列线程数为 ->4线程名称为 ->pool-1-thread-1
当前队列线程数为 ->3线程名称为 ->pool-1-thread-1
当前队列线程数为 ->2线程名称为 ->pool-1-thread-1
当前队列线程数为 ->1线程名称为 ->pool-1-thread-1
当前队列线程数为 ->0线程名称为 ->pool-1-thread-1

无解队列的最大线程数maxPoolSize其实是没有用的,我们可以看到他会一直网队列里扔,直到内存不够用,所以在我们不清楚具体要成熟多少的线程的时候,这个还是慎用

优先任务队列 PriorityBlockingQueue

先看代码:

扫描二维码关注公众号,回复: 8641489 查看本文章
package com.example.demo;


import java.util.Random;
import java.util.concurrent.*;

public class ThreadPoolPriority {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        PriorityBlockingQueue<Runnable> runnables = new PriorityBlockingQueue<Runnable>();
        threadService = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.SECONDS,
                runnables, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 10; i++) {
            threadService.execute(new ThreadTaskPriority(i, runnables));
        }
    }
}

class ThreadTaskPriority implements Runnable, Comparable<ThreadTaskPriority> {

    private int priority;

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    public ThreadTaskPriority(int priority) {
        this.priority = priority;
    }

    @Override
    public int compareTo(ThreadTaskPriority o) {
        return this.priority > o.priority ? -1 : 1;
    }

    PriorityBlockingQueue<Runnable> runnables;

    public ThreadTaskPriority(int priority, PriorityBlockingQueue<Runnable> runnables) {
        this.priority = priority;
        this.runnables = runnables;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("priority -> " + this.priority + " 当前队列线程数为 ->" + runnables.size() + "线程名称为 ->" + Thread.currentThread().getName());
    }
}

结果为:

priority -> 0 当前队列线程数为 ->9线程名称为 ->pool-1-thread-1
priority -> 9 当前队列线程数为 ->8线程名称为 ->pool-1-thread-1
priority -> 8 当前队列线程数为 ->7线程名称为 ->pool-1-thread-1
priority -> 7 当前队列线程数为 ->6线程名称为 ->pool-1-thread-1
priority -> 6 当前队列线程数为 ->5线程名称为 ->pool-1-thread-1
priority -> 5 当前队列线程数为 ->4线程名称为 ->pool-1-thread-1
priority -> 4 当前队列线程数为 ->3线程名称为 ->pool-1-thread-1
priority -> 3 当前队列线程数为 ->2线程名称为 ->pool-1-thread-1
priority -> 2 当前队列线程数为 ->1线程名称为 ->pool-1-thread-1
priority -> 1 当前队列线程数为 ->0线程名称为 ->pool-1-thread-1

可以看出来,除了第一个任务i=0的时候是第一个执行的,剩下的线程都是根据priority大小倒序执行的。他也是一个无界队列,只不过是有一个特定的顺序,他必须要实现一个Comparable接口,来判断谁在前边执行,这个类似于我们的抢票了,登记越高,抢票越靠前。

拒绝策略

上边的四个队列我们用的拒绝策略都是java默认的拒绝策略,也就是达到条件以后会立刻失败,阻止程序的运行。接下来我们讲一下其他的策略和自定义策略。

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作
  • CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行
  • DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交
  • DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
    当然上边的接口都实现了RejectedExecutionHandler接口,我们自定也的拒绝策略也是执行,自定义拒绝策略如下
package com.example.demo;


import java.util.concurrent.*;

public class ThreadPool {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        ArrayBlockingQueue<Runnable> runnables = new ArrayBlockingQueue<Runnable>(5);
        threadService = new ThreadPoolExecutor(1,2, 1000, TimeUnit.SECONDS,
                runnables ,Executors.defaultThreadFactory(), new RejectHandler());

        for (int i = 0; i < 10; i++) {
            threadService.execute(new ThreadTask(runnables));
        }
    }
}

class RejectHandler implements RejectedExecutionHandler {
    
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(r.toString() + "拒绝请求");
    }
}

class ThreadTask implements Runnable {

    ArrayBlockingQueue<Runnable> runnables;

    public ThreadTask(ArrayBlockingQueue<Runnable> runnables) {
        this.runnables = runnables;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前队列线程数为 ->"+ runnables.size() + "线程名称为 ->" + Thread.currentThread().getName());
    }
}

结果如下:

com.example.demo.ThreadTask@6a5fc7f7拒绝请求
com.example.demo.ThreadTask@3b6eb2ec拒绝请求
com.example.demo.ThreadTask@1e643faf拒绝请求
当前队列线程数为 ->5线程名称为 ->pool-1-thread-2
当前队列线程数为 ->4线程名称为 ->pool-1-thread-1
当前队列线程数为 ->3线程名称为 ->pool-1-thread-2
当前队列线程数为 ->3线程名称为 ->pool-1-thread-1
当前队列线程数为 ->1线程名称为 ->pool-1-thread-2
当前队列线程数为 ->1线程名称为 ->pool-1-thread-1
当前队列线程数为 ->0线程名称为 ->pool-1-thread-2

##自定义线程创建ThreadFactory
我们传进去的是runnable,然后线程池会为我们创建线程,默认使用的是Executors.defaultThreadFactory(),代码如下:

 static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

当然我们也可以自定义,只要继承了ThreadFactory就可以,例如

class ThreadFactoryDemo implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        System.out.println("线程" + r.hashCode() + "创建");
        return new Thread(r,"threadPool" + r.hashCode());
    }
}

然后我们定义线程池的时候,把以前的Executors.defaultThreadFactory()替换成new ThreadFactoryDemo()就可以了。返回结果如下:

线程2093176254创建
线程1854731462创建
com.example.demo.ThreadTask@12edcd21拒绝请求
com.example.demo.ThreadTask@34c45dca拒绝请求
com.example.demo.ThreadTask@52cc8049拒绝请求
当前队列线程数为 ->5线程名称为 ->threadPool1854731462
当前队列线程数为 ->4线程名称为 ->threadPool2093176254
当前队列线程数为 ->3线程名称为 ->threadPool1854731462
当前队列线程数为 ->2线程名称为 ->threadPool2093176254
当前队列线程数为 ->1线程名称为 ->threadPool1854731462
当前队列线程数为 ->0线程名称为 ->threadPool2093176254
当前队列线程数为 ->0线程名称为 ->threadPool1854731462

##扩展ThreadPoolExecutor
hreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的,

  • beforeExecute:线程池中任务运行前执行
  • afterExecute:线程池中任务运行完毕后执行
  • terminated:线程池退出后执行
    通过这三个方法,我们可以监控任务的执行情况,代码如下
package com.example.demo;


import java.util.concurrent.*;

public class ThreadPoolBefore {

    private static ExecutorService threadService;

    public static void main(String[] args) {
        ArrayBlockingQueue<Runnable> runnables = new ArrayBlockingQueue<Runnable>(30);
        threadService = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.SECONDS,
                runnables, new ThreadFactoryDemo(), new ThreadPoolExecutor.AbortPolicy()) {
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行:" + ((ThreadTaskBefore) r).getTaskName());
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("执行结束:" + ((ThreadTaskBefore) r).getTaskName());
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };

        for (int i = 0; i < 10; i++) {
            threadService.execute(new ThreadTaskBefore(runnables, "task" + i));
        }

        threadService.shutdown();

    }
}


class ThreadTaskBefore implements Runnable {

    ArrayBlockingQueue<Runnable> runnables;

    public ThreadTaskBefore(ArrayBlockingQueue<Runnable> runnables) {
        this.runnables = runnables;
    }

    private String taskName;

    public String getTaskName() {
        return taskName;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public ThreadTaskBefore(ArrayBlockingQueue<Runnable> runnables, String taskName) {
        this.runnables = runnables;
        this.taskName = taskName;
    }

    @Override
    public void run() {

        System.out.println("当前队列线程数为 ->" + runnables.size() + "线程名称为 ->" + Thread.currentThread().getName());
    }
}

执行效果如下:

线程1134712904创建
准备执行:task0
当前队列线程数为 ->9线程名称为 ->threadPool1134712904
执行结束:task0
准备执行:task1
当前队列线程数为 ->8线程名称为 ->threadPool1134712904
执行结束:task1
准备执行:task2
当前队列线程数为 ->7线程名称为 ->threadPool1134712904
执行结束:task2
准备执行:task3
当前队列线程数为 ->6线程名称为 ->threadPool1134712904
执行结束:task3
准备执行:task4
当前队列线程数为 ->5线程名称为 ->threadPool1134712904
执行结束:task4
准备执行:task5
当前队列线程数为 ->4线程名称为 ->threadPool1134712904
执行结束:task5
准备执行:task6
当前队列线程数为 ->3线程名称为 ->threadPool1134712904
执行结束:task6
准备执行:task7
当前队列线程数为 ->2线程名称为 ->threadPool1134712904
执行结束:task7
准备执行:task8
当前队列线程数为 ->1线程名称为 ->threadPool1134712904
执行结束:task8
准备执行:task9
当前队列线程数为 ->0线程名称为 ->threadPool1134712904
执行结束:task9
线程池退出

可以看到通过对beforeExecute()、afterExecute()和terminated()的实现,我们对线程池中线程的运行状态进行了监控,在其执行前后输出了相关打印信息。另外使用shutdown方法可以比较安全的关闭线程池, 当线程池调用该方法后,线程池中不再接受后续添加的任务。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

发布了176 篇原创文章 · 获赞 84 · 访问量 44万+

猜你喜欢

转载自blog.csdn.net/lovemenghaibin/article/details/101278119