java线程池案例

简介

线程Thread是一个重量级资源,线程的创建、启动以及销毁都是比较耗费系统资源的,同时受限于系统资源的限制,线程的数量与系统性能是一种抛物线的关系,因此对线程的管理,是一种非常好的程序设计习惯,自JDK1.5起,utils包提供了ExecutorService[ɪɡˈzɛkjətɚ]线程池的实现。通俗的将:为了避免重复的创建线程,线程池的出现可以让线程进行复用。当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池的作用

1.重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
2.能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
3.能够多线程进行简单的管理,使线程的使用简单、高效。

线程池的创建

Java里面线程池的顶级接口是Executor,通过工具类java.util.concurrent.Executors的静态方法来创建。Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

Executors工具类创建线程池

方法名功能
newFixedThreadPool(int nThreads)创建固定大小的线程池
newSingleThreadExecutor()创建只有一个线程的线程池
newCachedThreadPool()创建一个不限线程数上限的线程池,任何提交的任务都将立即执行

newFixedThreadPool:

        使用的构造方式为new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),设置了corePoolSize=maxPoolSize,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入,当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常


newSingleThreadExector:

        使用的构造方式为new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是将线程数设置为了1,单线程,弊端和newFixedThreadPool一致


newCachedThreadPool:

        使用的构造方式为new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize为很大的数,同步移交队列,也就是说不维护常驻线程(核心线程),每次来请求直接创建新线程来处理任务,也不使用队列缓冲,会自动回收多余线程,由于将maxPoolSize设置成Integer.MAX_VALUE,当请求很多时就可能创建过多的线程,导致资源耗尽OOM


newScheduledThreadPool:

        使用的构造方式为new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定时周期性执行,注意一下使用的是延迟队列,弊端同newCachedThreadPool一致

所以根据上面分析我们可以看到,FixedThreadPool和SigleThreadExecutor中之所以用LinkedBlockingQueue无界队列,是因为设置了corePoolSize=maxPoolSize,线程数无法动态扩展,于是就设置了无界阻塞队列来应对不可知的任务量;而CachedThreadPool则使用的是SynchronousQueue同步移交队列,为什么使用这个队列呢?因为CachedThreadPool设置了corePoolSize=0,maxPoolSize=Integer.MAX_VALUE,来一个任务就创建一个线程来执行任务,用不到队列来存储任务;SchduledThreadPool用的是延迟队列DelayedWorkQueue。在实际项目开发中也是推荐使用手动创建线程池的方式,而不用默认方式,关于这点在《阿里巴巴开发规范》
中是这样描述的:

ThreadPoolExecutor构造方法创建

Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法

// Java线程池的构造函数
public ThreadPoolExecutor(
  int corePoolSize, // 核心线程数
  int maximumPoolSize, // 最大线程数
  long keepAliveTime,  //线程活动时间
  TimeUnit unit, // 时间单位
  BlockingQueue<Runnable> workQueue, //工作队列
  ThreadFactory threadFactory, // 线程工厂
  RejectedExecutionHandler handler) //拒绝策略

线程池中几个重要的参数 corePoolSize, maximumPoolSize, workQueue以及handler:

ThreadPoolExecutor参数详解

corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务

maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)

keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);

unit:keepAliveTime的时间单位

workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中

threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建

handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy

workQueue队列

SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务
 

handler拒绝策略

  • AbortPolicy:中断抛出异常
  • DiscardPolicy:默默丢弃任务,不进行任何通知
  • DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
  • CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好)

线程池中的线程创建流程图:

线程池的工作顺序

If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

线程池提交任务:

可以向线程池提交的任务有两种:RunnableCallable,二者的区别如下:

提交方式是否关心返回结果
Future<T> submit(Callable<T> task)
void execute(Runnable command)
Future<?> submit(Runnable task)否,虽然返回Future,但是其get()方法总是返回null
Future<?> submit(Runnable task)否,虽然返回Future,但是其get()方法总是返回null

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);

Runnable和Callable区别

  1. 方法签名不同,void Runnable.run()V Callable.call() throws Exception
  2. 是否允许有返回值,Callable允许有返回值
  3. 是否允许抛出异常,Callable允许抛出异常。

Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。

ThreadPoolExecutor创建线程池案例

execute方法提交

   /**
     * <p>
     * 使用execute方法执行任务,通过Runnable接口创建线程类(匿名内部类方式创建线程类)
     * (1)定义runnable接口的实现类,并重写该接口的run()方法,
     * (2)创建 Runnable实现类的实例,
     * </P>
     */
    @Test
    public void createThreadPool1() {

        int pcount = Runtime.getRuntime().availableProcessors();
        //最大线程数控制
        int maxthreadNum = 5;
        ExecutorService executor = new ThreadPoolExecutor(pcount, maxthreadNum, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 5; i++) {
            final int index = i;
            //匿名内部类方式创建
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    //业务处理
                    System.out.println(Thread.currentThread().getName() + " " + index);
                }
            });
        }
    }

 /**
     * <p>
     * 使用execute方法执行任务,通过Runnable接口创建线程类(自定义创建线程类)
     * (1)定义runnable接口的实现类,并重写该接口的run()方法,
     * (2)创建 Runnable实现类的实例,
     * </P>
     */

    @Test
    public void createThreadPool2() {

        int pcount = Runtime.getRuntime().availableProcessors();
        //最大线程数控制
        int maxthreadNum = 5;
        ExecutorService executor = new ThreadPoolExecutor(pcount, maxthreadNum, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 5; i++) {
            final int index = i;
            executor.execute(new RunnableTask(index));
        }
    }

    static class RunnableTask implements Runnable {
        private int i;

        public RunnableTask(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            //业务处理
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

invokeAll方法批量提交

  /**
     * <p>
     * 使用invokeAll方法批量执行任务,通过Callable接口创建线程类(匿名内部类方式创建线程类)
     * invokeAll的作用是:等待所有的任务执行完成后统一返回。
     * (1)定义runnable接口的实现类,并重写该接口的run()方法,
     * (2)创建 Runnable实现类的实例,
     * </P>
     */
    @Test
    public void createThreadPool3() {

        ExecutorService executor = new ThreadPoolExecutor(4, 4, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        List<Callable<Object>> tasks = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            tasks.add(() -> {
                System.out.println(Thread.currentThread().getName());
                return null;
            });
        }
        try {
            List<Future<Object>> futureList = executor.invokeAll(tasks);
            // 获取全部并发任务的运行结果
            for (Future f : futureList) {
                // 获取任务的返回值,并输出到控制台
                System.out.println("result:" + f.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 关闭线程池
        executor.shutdown();
    }

使用submit方法提交

 /**
     * <p>
     * 使用submit方法执行任务,通过Callable接口创建线程类(匿名内部类方式创建线程类)
     * </P>
     */
    @Test
    public void createThreadPool4() {

        ExecutorService executor = new ThreadPoolExecutor(4, 4, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        List<Future<Callable>> futureList = new ArrayList<>(10);

        for (int i = 0; i < 5; i++) {
            final int index = i;
            Future future = executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " " + index);
                return index;
            });
            futureList.add(future);
        }
        try {
            // 获取全部并发任务的运行结果
            for (Future f : futureList) {
                // 获取任务的返回值,并输出到控制台
                System.out.println("result:" + f.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 关闭线程池
        executor.shutdown();
    }

    /**
     * <p>
     * 使用submit方法执行任务,通过Callable接口创建线程类(自定义方式创建线程类)
     * </P>
     */

    @Test
    public void createThreadPool5() {

        ExecutorService executor = new ThreadPoolExecutor(4, 4, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        List<Future<Callable>> futureList = new ArrayList<>(10);

        for (int i = 0; i < 5; i++) {
            final int index = i;
            Future future = executor.submit(new CallableTask1(index));
            futureList.add(future);
        }
        try {
            // 获取全部并发任务的运行结果
            for (Future f : futureList) {
                // 获取任务的返回值,并输出到控制台
                System.out.println("result:" + f.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 关闭线程池
        executor.shutdown();
    }

    static class CallableTask1 implements Callable<Integer> {
        Integer i;

        public CallableTask1(Integer i) {
            this.i = i;
        }

        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + " " + i);
            return i;
        }
    }
}

线程池监控

线程池使用不当也会使服务器资源枯竭,导致异常情况的发生,比如固定线程池的阻塞队列任务数量过多、缓存线程池创建的线程过多导致内存溢出、系统假死等问题。因此,我们需要一种简单的监控方案来监控线程池的使用情况,比如完成任务数量、未完成任务数量、线程大小等信息。

一、线程池监控参数

线程池提供了以下几个方法可以监控线程池的使用情况:

方法含义
getActiveCount()线程池中正在执行任务的线程数量
getCompletedTaskCount()线程池已完成的任务数量,该值小于等于taskCount
getCorePoolSize()线程池的核心线程数量
getLargestPoolSize()线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize
getMaximumPoolSize()线程池的最大线程数量
getPoolSize()线程池当前的线程数量
getTaskCount()线程池已经执行的和未执行的任务总数

二、线程池监控案例

  /**
     * <p>
     * 实例2:打印线程池参数
     * </P>
     */
    @Test
    public void printThreadPoolParameters() {

        // 开始时间
        long start = System.currentTimeMillis();
        int pcount = Runtime.getRuntime().availableProcessors();
        // 创建一个线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                printThreadPoolStatus(executor);
            });
        }
        executor.shutdown();
        System.out.println("执行任务消耗了 :" + (System.currentTimeMillis() - start) + "毫秒");
    }


 private static void printThreadPoolStatus(ThreadPoolExecutor executor) {
        BlockingQueue queue = executor.getQueue();
        System.out.println(Thread.currentThread().getName() + "," +

                "当前的线程数量:" + executor.getPoolSize() + "," +
                "核心线程数:" + executor.getCorePoolSize() + "," +
                "最大线程数:" + executor.getMaximumPoolSize() + "," +
                "活动线程数:" + executor.getActiveCount() + "," +
                "任务总数:" + executor.getTaskCount() + "," +
                "任务完成数:" + executor.getCompletedTaskCount() + "," +
                "线程空闲时间:" + executor.getKeepAliveTime(TimeUnit.SECONDS) + "秒," +
                "当前排队线程数:" + queue.size() + "," +
                "队列剩余大小:" + queue.remainingCapacity() + "," +
                "线程池是否关闭:" + executor.isShutdown() + ","
        );
    }

三.测试结果

pool-1-thread-1,当前的线程数量:3,核心线程数:4,最大线程数:4,活动线程数:3,任务总数:3,任务完成数:0,线程空闲时间:60秒,当前排队线程数:0,队列剩余大小:10,线程池是否关闭:false,
pool-1-thread-1,当前的线程数量:4,核心线程数:4,最大线程数:4,活动线程数:4,任务总数:5,任务完成数:1,线程空闲时间:60秒,当前排队线程数:0,队列剩余大小:10,线程池是否关闭:true,
pool-1-thread-4,当前的线程数量:3,核心线程数:4,最大线程数:4,活动线程数:3,任务总数:5,任务完成数:2,线程空闲时间:60秒,当前排队线程数:0,队列剩余大小:10,线程池是否关闭:true,
pool-1-thread-3,当前的线程数量:2,核心线程数:4,最大线程数:4,活动线程数:2,任务总数:5,任务完成数:3,线程空闲时间:60秒,当前排队线程数:0,队列剩余大小:10,线程池是否关闭:true,
pool-1-thread-2,当前的线程数量:2,核心线程数:4,最大线程数:4,活动线程数:2,任务总数:5,任务完成数:4,线程空闲时间:60秒,当前排队线程数:0,队列剩余大小:10,线程池是否关闭:true,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值