美文网首页Java面试
多线程2,线程池深入理解

多线程2,线程池深入理解

作者: 杨充211 | 来源:发表于2018-04-17 19:04 被阅读33次

    目录介绍

    • 1.ThreadPoolExecutor类介绍
    • 1.1 构造函数
    • 1.2 参数解析
    • 1.3 遵循的规则
    • 1.4 使用线程池管理线程的优点
    • 2.关于线程池的分类
    • 2.1 FixedThreadPool
    • 2.2 CachedThreadPool
    • 2.3 ScheduledThreadPool
    • 2.4 SingleThreadExecutor
    • 3.线程池一般用法
    • 3.1 一般方法介绍
    • 3.2 newFixedThreadPool的使用
    • 3.3 newSingleThreadExecutor的使用
    • 3.4 newCachedThreadPool的使用
    • 3.5 newScheduledThreadPool的使用
    • 3.6 线程创建规则
    • 4.线程池封装
    • 4.1 具体可以参考下篇文章
    • 4.2 参考博客

    前言介绍

    1.ThreadPoolExecutor类介绍

    1.1 构造函数

    • ExecutorService是最初的线程池接口,ThreadPoolExecutor类是对线程池的具体实现,它通过构造方法来配置线程池的参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    

    1.2 参数解析

    • corePoolSize,线程池中核心线程的数量,默认情况下,即使核心线程没有任务在执行它也存在的,我们固定一定数量的核心线程且它一直存活这样就避免了一般情况下CPU创建和销毁线程带来的开销。我们如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程就会有超时策略,这个时间由keepAliveTime来设定,即keepAliveTime时间内如果核心线程没有回应则该线程就会被终止。allowCoreThreadTimeOut默认为false,核心线程没有超时时间。
    • maximumPoolSize,线程池中的最大线程数,当任务数量超过最大线程数时其它任务可能就会被阻塞。最大线程数=核心线程+非核心线程。非核心线程只有当核心线程不够用且线程池有空余时才会被创建,执行完任务后非核心线程会被销毁。
    • keepAliveTime,非核心线程的超时时长,当执行时间超过这个时间时,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,此属性也作用在核心线程上。
    • unit,枚举时间单位,TimeUnit。
    • workQueue,线程池中的任务队列,我们提交给线程池的runnable会被存储在这个对象上。

    1.3 遵循的规则

    • 当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
    • 如果线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行;
    • 如果在上一步骤中任务队列已满但是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务;
    • 如果上一步骤中线程数量达到了限定线程总量,那么线程池则拒绝执行该任务,且ThreadPoolExecutor会调用RejectedtionHandler的rejectedExecution方法来通知调用者。

    1.4 使用线程池管理线程的优点

    • 1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
    • 2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量
    • 3、在执行大量异步任务时提高了性能
    • 4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

    2.关于线程池的分类

    2.1 FixedThreadPool

    • 通过Executors的newFixedThreadPool()方法创建,它是个线程数量固定的线程池,该线程池的线程全部为核心线程,它们没有超时机制且排队任务队列无限制,因为全都是核心线程,所以响应较快,且不用担心线程会被回收。

    2.2 CachedThreadPool

    • 通过Executors的newCachedThreadPool()方法来创建,它是一个数量无限多的线程池,它所有的线程都是非核心线程,当有新任务来时如果没有空闲的线程则直接创建新的线程不会去排队而直接执行,并且超时时间都是60s,所以此线程池适合执行大量耗时小的任务。由于设置了超时时间为60s,所以当线程空闲一定时间时就会被系统回收,所以理论上该线程池不会有占用系统资源的无用线程。

    2.3 ScheduledThreadPool

    • 通过Executors的newScheduledThreadPool()方法来创建,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,但是它的非核心线程超时时间是0s,所以非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

    2.4 SingleThreadExecutor

    • 通过Executors的newSingleThreadExecutor()方法来创建,它内部只有一个核心线程,它确保所有任务进来都要排队按顺序执行。它的意义在于,统一所有的外界任务到同一线程中,让调用者可以忽略线程同步问题。

    3.线程池一般用法

    3.1 一般方法介绍

    • shutDown(),关闭线程池,需要执行完已提交的任务;
    • shutDownNow(),关闭线程池,并尝试结束已提交的任务;
    • allowCoreThreadTimeOut(boolen),允许核心线程闲置超时回收;
    • execute(),提交任务无返回值;
    • submit(),提交任务有返回值;

    3.2 newFixedThreadPool的使用

    • 3.2.1 创建一个newFixedThreadPool线程池
    private void newFixedThreadPool() {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 1; i <= 20; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Log.e("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    • 3.2.2 打印日志如下
    • 创建了一个线程数为5的固定线程数量的线程池,同理该线程池支持的线程最大并发数也是5,模拟20个任务让它处理,执行任务。最后我们获取线程的信息,打印日志。
    • 日志如图所示:


      image

    3.3 newSingleThreadExecutor的使用

    • 3.3.1 创建一个newSingleThreadExecutor线程池
    private void newSingleThreadExecutor() {
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= number; i++) {
            final int index = i;
            singleThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Log.v("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    • 3.3.2 打印日志如下
    • 改了线程池的实现方式,即依次一个一个的处理任务,而且都是复用一个线程,日志为


      image

    3.4 newCachedThreadPool的使用

    • 3.4.1 创建一个newCachedThreadPool线程池
    private void newCachedThreadPool() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= number; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Log.v("潇湘剑雨newCachedThreadPool", "线程:" + threadName + ",正在执行第" + index + "个任务");
                    try {
                        long time = index * 500;
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    • 3.4.2 打印日志如下
    • 为了体现该线程池可以自动根据实现情况进行线程的重用,而不是一味的创建新的线程去处理任务,我设置了每隔1s去提交一个新任务,这个新任务执行的时间也是动态变化的,所以,效果为:


      image

    3.5 newScheduledThreadPool的使用

    • 3.5.1 创建一个newScheduledThreadPool线程池
    private void newScheduledThreadPool() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //延迟2秒后执行该任务
        scheduledThreadPool.schedule(new Runnable() {
            @SuppressLint("LongLogTag")
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.e("潇湘剑雨newScheduledThreadPool", "线程:" + threadName + ",正在执行");
            }
        }, 2, TimeUnit.SECONDS);
        //延迟1秒后,每隔2秒执行一次该任务
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.e("潇湘剑雨", "线程:" + threadName + ",正在执行");
            }
        }, 1, 2, TimeUnit.SECONDS);
    }
    
    • 3.5.2 打印日志如下
    • 通过日志可以发现schedule方法的任务只是执行了一次,然后每隔2秒执行一次该scheduleAtFixedRate方法中的任务


      image

    3.6 线程创建规则

    • ThreadPoolExecutor对象初始化时,不创建任何执行线程,当有新任务进来时,才会创建执行线程。构造ThreadPoolExecutor对象时,需要配置该对象的核心线程池大小和最大线程池大小
      1. 当目前执行线程的总数小于核心线程大小时,所有新加入的任务,都在新线程中处理。
      1. 当目前执行线程的总数大于或等于核心线程时,所有新加入的任务,都放入任务缓存队列中。
      1. 当目前执行线程的总数大于或等于核心线程,并且缓存队列已满,同时此时线程总数小于线程池的最大大小,那么创建新线程,加入线程池中,协助处理新的任务。
      1. 当所有线程都在执行,线程池大小已经达到上限,并且缓存队列已满时,就rejectHandler拒绝新的任务。

    4.线程池封装

    4.1 具体可以参考下篇文章

    4.2 参考博客

    相关文章

      网友评论

        本文标题:多线程2,线程池深入理解

        本文链接:https://www.haomeiwen.com/subject/tzmpkftx.html