Java线程池之ThreadPoolExecutor详解

Java线程池

说起线程池,就得说一下“池”思想了,先有思想,后有实体,对于面向对象语言来讲,思想是尤为重要的一点,理解了思想,那么实体操作或者理解起来就变得相对容易一些。
个人理解的池,就是一种循环利用的思想,而循环利用的目的,是为了增加效率,避免频繁的创建销毁线程带来的cpu的开销,举个例子:我们做菜,肯定是把所有用到的材料工具拿起来放到案板上,然后一顿操作猛如虎,做完用完菜刀用砍刀,用完砍刀用剃刀的各种循环用的,做完菜了,收工的时候,把刀擦干净放到柜子里,这是一个家庭主夫的日常操作,我们总不能切辣椒的时候,从柜子里拿出来菜刀,切完,洗干净,放到柜子里,再切白菜的时候,再从柜子里拿出来这样子操作把?线程池也是一样的道理,它省去了我们很多的拿刀洗刀放刀的过程,而是把创建好的线程一个个的都放在案板(“内存中”)上,这样,我们需要用的时候,直接拿来用,用完就继续放着等待下次再用,午饭做完了,好,收拾一顿,等到作晚饭的时候再这样操作一顿。这就是劳动人民日常生活积累的宝贵经验,只不过这里的劳动人民是cpu罢了。

jdbc连接池同理,池子都差不多。

线程池定义:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

为什么要使用线程池之较为官方的解释:

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
第一:创建麻烦销毁麻烦;第二:收不住创建多了对jvm内存区域占用过大;

说了这么多,进行一波实操:

Exector

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

在java.util.concurrent中,java的并发包下
Java线程池之ThreadPoolExecutor详解_第1张图片
一个接口,定义了一个excute方法,参数是实现了runnable类的对象
Excutor接口是线程池的顶级接口,但从某些方面来讲Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService,Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。

ExecutorService

继承了Executor接口,并有下面几个方法:

public interface ExecutorService extends Executor {
	void shutdown(); //:关闭线程池
	
	boolean isShutdown(); //: 判断线程池是否关闭

	boolean isTerminated();//:判断所有任务是否已经完成,如果完成返回true

	boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException; //: 等待任务完成
        
	 List> invokeAll(Collection> tasks)
        throws InterruptedException; //: 唤醒所有任务
	
	......
}

先来看ExecutorService的默认实现:ThreadPoolExecutor

public class ThreadPoolExecutor extends AbstractExecutorService { }

它继承了一个实现了ExecutorService的类
来看它的构造方法:
Java线程池之ThreadPoolExecutor详解_第2张图片
针对这几个参数,我做一个详细的解释:

corePoolSize:线程池中核心线程数的最大值
maximumPoolSize:线程池中能拥有最多线程数
workQueue:用于缓存任务的阻塞队列
keepAliveTime:表示空闲线程的存活时间
TimeUnitunit:表示keepAliveTime的单位

if (来了一个新任务)
(1):如果没有空闲的线程执行该任务且当前的运行的线程数少于corePoolSize,则添加新的线程执行该任务
(2):如果没有空闲的线程执行该任务且当前的线程数等于核心线程数同时阻塞队列未满,将任务加入队列,而不加入新的线程
(3):如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于线程池中能拥有的最多线程数,则创建新的线程执行该任务
(4):如果核心池满了,阻塞队列也满了,同时线程的数量已经达到了线程池中最多拥有的线程数了,则交给构造函数的defaulthandler指定的策略来拒绝新任务
keepAliveTime参数指定的空闲线程的存活时间,就是当任务来的特别多的时候,核心池满了,阻塞队列也满了,而后面线程的数量也达到了线程池能拥有的最多线程池的数量之后,执行一段时间任务后空闲下来,空闲时长超过keepAliveTime后就会销毁这些线程,直至线程池中线程数量保证到corePoolSize的数量。

针对threadPoolExecutor的指定拒绝策略,handler默认的处理方式是抛出异常(在不传递handler参数的情况下,传递defaultHadler)

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy(); //: 抛出RejectedExecutionException异常

如果我们使用了带拒绝策略的构造函数,那么有以下几种取值可选择:

public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

这种方式会由想线程池提交任务的线程来执行该任务

public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

这种方式会抛弃当前的任务

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

这种方式会抛弃最旧的任务(最先提交而没有得到执行的任务)

workQueue参数决定了缓存任务的排队策略,对于不同的应用场景我们会采取不同的排队策略,这就需要不同类型的阻塞队列,在线程池中常用的阻塞队列有以下两种

(1):SynchronousQueue:此队列不缓存任何一个任务,向线程池提交任务时候,如果没有空闲线程来运行任务,则入列操作会阻塞,当有线程来获取任务时,出列操作会唤醒执行入列操作的线程。从这特性来看,这是一个无界队列,因此当使用SynchronousQueue作为线程池的阻塞队列时候,蚕食maximumPoolSizes没有作用
(2):LinkedBlockingQueue:链表实现的队列,可以是有界的,也可以是无界的,在Executors中默认是无界的。

Executors中的线程池的工厂方法(选取常用的作解释)

为了更加方便简洁的创建ThreadPoolExecutor对象,Java定义了Executors类,Executors类提供了创建常用配置线程池的方法:

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

返回ThreadPoolExecutor的父类实现的接口的格式,需要指定核心线程数,核心线程数和最大线程数相同,使用LInkedBlockingQueue作为阻塞队列,队列无界,线程空闲时间0秒(这种类型的线程池可以用于cpu密集的工作,这种工作中cpu忙于计算很少空闲,由于cpu能真正并发执行的线程数是一定的,对于那些需要cpu进行大量计算的线程,创建的线程数超过cpu能整整并发执行的线程数没有太大的意义)

newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

使用synchronousQueue作为阻塞队列,队列无界,线程的空闲时限未60秒,这种类型的线程池适合IO密集的服务,因为io请求具有密集,数量巨大,不持续,服务端cpu等待io响应时间长等特点,服务端为了提高cpu的使用率就应该未每个io请求都创建一个线程。这个方法还有一个重载的方法,传递ThreadFactory,官方的解释是:the factory to user when creating new threads

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

这里面的ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,创建一个指定核心池大小的线程池,可以做一个定时执行的线程池

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

线程池中只有一个线程工作,阻塞队列用的是LinkdeblockingQueue,它能保证任务按提交的顺序来执行任务

Executors就是一个线程工具类,提供了一些静态工厂,要么是ThreadpoolExecutor的方法,要么是其子类的方法,对各种各样不同参数的组合情况下创建的不同线程池做了封装,让我们更方便的使用线程池而已

随便写一个测试类来搞一下

package com.wudiqiang.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author: 着凉的皮皮虾
 * @Date: 2019/7/27 20:09
 */
public class ThreadDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        for (int i = 0; i < 100; i++) {
            System.out.println("这是第" + i + "个从线程池执行的线程!");
            executorService.execute(runnable);
        }
    }
}

从运行结果可以看出,虽然我们从线程池调用了一百次线程,但是执行的线程总数就是我们指定的核心池的这10个

这是第0个从线程池执行的线程!
这是第1个从线程池执行的线程!
这是第2个从线程池执行的线程!
这是第3个从线程池执行的线程!
pool-1-thread-1
这是第4个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-3
这是第5个从线程池执行的线程!
pool-1-thread-4
这是第6个从线程池执行的线程!
pool-1-thread-5
pool-1-thread-6
这是第7个从线程池执行的线程!
pool-1-thread-7
这是第8个从线程池执行的线程!
这是第9个从线程池执行的线程!
pool-1-thread-8
这是第10个从线程池执行的线程!
pool-1-thread-9
这是第11个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-10
pool-1-thread-2
这是第12个从线程池执行的线程!
这是第13个从线程池执行的线程!
pool-1-thread-3
pool-1-thread-4
这是第14个从线程池执行的线程!
这是第15个从线程池执行的线程!
pool-1-thread-5
pool-1-thread-6
这是第16个从线程池执行的线程!
这是第17个从线程池执行的线程!
pool-1-thread-7
pool-1-thread-8
这是第18个从线程池执行的线程!
这是第19个从线程池执行的线程!
pool-1-thread-1
pool-1-thread-9
这是第20个从线程池执行的线程!
pool-1-thread-10
这是第21个从线程池执行的线程!
这是第22个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-3
这是第23个从线程池执行的线程!
这是第24个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第25个从线程池执行的线程!
这是第26个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第27个从线程池执行的线程!
这是第28个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第29个从线程池执行的线程!
这是第30个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-10
这是第31个从线程池执行的线程!
这是第32个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-3
这是第33个从线程池执行的线程!
这是第34个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第35个从线程池执行的线程!
这是第36个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第37个从线程池执行的线程!
这是第38个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第39个从线程池执行的线程!
这是第40个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-10
这是第41个从线程池执行的线程!
这是第42个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-3
这是第43个从线程池执行的线程!
这是第44个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第45个从线程池执行的线程!
这是第46个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第47个从线程池执行的线程!
pool-1-thread-8
这是第48个从线程池执行的线程!
这是第49个从线程池执行的线程!
pool-1-thread-1
pool-1-thread-9
这是第50个从线程池执行的线程!
这是第51个从线程池执行的线程!
pool-1-thread-10
pool-1-thread-2
这是第52个从线程池执行的线程!
这是第53个从线程池执行的线程!
pool-1-thread-3
这是第54个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第55个从线程池执行的线程!
这是第56个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第57个从线程池执行的线程!
这是第58个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第59个从线程池执行的线程!
这是第60个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-10
这是第61个从线程池执行的线程!
这是第62个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-3
这是第63个从线程池执行的线程!
这是第64个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第65个从线程池执行的线程!
这是第66个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第67个从线程池执行的线程!
这是第68个从线程池执行的线程!
pool-1-thread-8
这是第69个从线程池执行的线程!
pool-1-thread-1
pool-1-thread-9
这是第70个从线程池执行的线程!
这是第71个从线程池执行的线程!
pool-1-thread-10
pool-1-thread-2
这是第72个从线程池执行的线程!
这是第73个从线程池执行的线程!
pool-1-thread-3
这是第74个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第75个从线程池执行的线程!
这是第76个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第77个从线程池执行的线程!
这是第78个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第79个从线程池执行的线程!
这是第80个从线程池执行的线程!
pool-1-thread-9
这是第81个从线程池执行的线程!
pool-1-thread-10
这是第82个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-3
这是第83个从线程池执行的线程!
这是第84个从线程池执行的线程!
pool-1-thread-4
pool-1-thread-5
这是第85个从线程池执行的线程!
这是第86个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第87个从线程池执行的线程!
这是第88个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第89个从线程池执行的线程!
这是第90个从线程池执行的线程!
pool-1-thread-2
pool-1-thread-10
这是第91个从线程池执行的线程!
这是第92个从线程池执行的线程!
pool-1-thread-9
pool-1-thread-3
这是第93个从线程池执行的线程!
这是第94个从线程池执行的线程!
pool-1-thread-4
这是第95个从线程池执行的线程!
pool-1-thread-5
这是第96个从线程池执行的线程!
pool-1-thread-6
pool-1-thread-7
这是第97个从线程池执行的线程!
这是第98个从线程池执行的线程!
pool-1-thread-8
pool-1-thread-1
这是第99个从线程池执行的线程!
pool-1-thread-2

明天继续写ExecutorService的子接口 ScheduledExecutorService接口 及 ScheduledThreadPoolExecutor类(继承ThreadPoolExecutor 实现ScheduleExecutorService接口,主要做周期性任务调度)

你可能感兴趣的:(多线程)