1.线程池简介

2.线程池应用

3.线程池参数详解

4.如何正当设置线程池参数

1.线程池简介
咱们先来介绍一下,什么是线程池?
在以往的博客中,咱们介绍线程的时候,无论是Runnable,还是Calllable这些,最终都是应用new Thread().start(); 的形式进行创立,其实在实在的生产环境中,咱们是禁止应用这种写法的,因为这样有几个重大的弊病:
a.每次new Thread() 新建对象,会造成开销,导致服务器运行性能变差。
b.在无限度new Thread()的状况下,咱们一不小心就会创立过多的线程,相互之间竞争资源,可能导致上下文频繁切换,占用资源过多,死锁或者oom。
c. 不足更多功能,如定时执行、定期执行、线程中断。

于是,JAVA线程池就产生了!

JAVA线程池做的次要工作是管制运行的线程的数量,解决过程中将工作放入队列而后在线程创立后启动这些工作,如果线程数量超过了最大执行数量,超出数量的线程将排队等待,期待其它线程执行结束,再从队列中取出工作来执行

它的次要特点:线程服用,管制最大并发数,治理线程。

第一:升高资源耗费,通过反复利用本人创立的线程升高线程创立和销毁造成的耗费。
第二:进步响应速度,当工作达到时,工作能够不须要等到线程创立就能立刻执行
第三:进步线程的可管理性,线程是稀缺资源,如果无限度地创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够对立地进行调配,调优和监控。

多说无用,咱们来看看如何应用JAVA线程池吧!

2.线程池应用
咱们先介绍一下JAVA自带的创立线程池的工具类!

Executors

它提供了三个办法来创立线程池:

        ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池固定数线程        ExecutorService threadPoo2 = Executors.newCachedThreadPool();//一池多线程        ExecutorService threadPoo3 = Executors.newSingleThreadExecutor();//一池一线程

顾名思义:
newFixedThreadPool()办法就是创立一个固定线程数的线程池,
newCachedThreadPool()办法会依据你计算机和工作的状况,进行主动增减线程
newSingleThreadExecutor()创立一个单线程的线程池,每次只能执行一个工作

同时创立完线程池之后,应用线程池自带的submit或者excute办法,而后用lambda表达式传入函数体后,就能够执行工作了!

        threadPool.submit();        threadPool.execute();

这时,咱们能够应用一个线程池尝试着应用一下

        ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池固定数线程        //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程        try {            for (int i = 1; i < 10000; i++) {                threadPool.submit(() -> {                    System.out.println(Thread.currentThread().getName() + "正在办理业务");                });            }        } catch (Exception e) {            e.printStackTrace();        } finally {            threadPool.shutdown();        }

咱们创立了一个线程池,固定数量为5,而后启动10000个线程,看看运行状况。

能够看出,线程编号始终没有超过咱们设置的初始值5。

这时,咱们能够换一个线程池应用一下,咱们应用cache线程池:

         ExecutorService threadPoo1 = Executors.newCachedThreadPool();//一池多线程        //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程        try {            for (int i = 1; i < 10000; i++) {                threadPoo1.submit(() -> {                    System.out.println(Thread.currentThread().getName() + "正在办理业务");                });            }        } catch (Exception e) {            e.printStackTrace();        } finally {            threadPoo1.shutdown();        }


能够看出,线程启动地越多,该线程池就会启动越多的线程来适应需要。

让咱们来看一下最初一个线程池:

        ExecutorService threadPoo1 = Executors.newSingleThreadExecutor();//一池一线程        //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程        try {            for (int i = 1; i < 10000; i++) {                threadPoo1.submit(() -> {                    System.out.println(Thread.currentThread().getName() + "正在办理业务");                });            }        } catch (Exception e) {            e.printStackTrace();        } finally {            threadPoo1.shutdown();        }

查看后果:

能够看出,永远只有一个线程在运行。

是不是十分神奇?!咱们能够看一下创立线程池的办法到底怎么执行地,竟然有这种成果!

首先看一下固定数线程的办法:

    public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, //常驻外围线程数                                      nThreads,//最大线程数                                      0L,//多余的闲暇线程的存活工夫                                       TimeUnit.MILLISECONDS,//多余线程存活单位                                      new LinkedBlockingQueue<Runnable>()); //应用的阻塞队列    }

cache线程池的办法:

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

单线程池的办法:

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

从我给的源码正文中能够看出,
固定数线程池最大和常驻线程数都是传入的参数
而cache线程池的最大线程数是Integer.MAX_VALUE
单线程池底层应用了SynchronousQueue : 不存储元素的阻塞队列,即单个元素的队列。
多线程池的底层应用了LinkedBlockingQueue : 由链表组成的有界(然而默认值为Integer.MAX_VALUE阻塞队列
阻塞队列能够看我之前写的这篇文章:JAVA并发编程——阻塞队列实践及其API介绍
所以就导致了为什么会有以上的运行后果!

所以在生产中,咱们到底如何应用呢??

答案是————一个都不应用,因为LinkedBlockingQueue它的最大长度是Integer.MAX_VALUE,那就相当于无界可能会让服务器内存爆仓产生OOM,所以接下来,咱们学习一下自定义创立线程池的参数!

3.线程池参数详解
咱们先点到ThreadPoolExecutor这个类里看一下,它到底有几个参数:

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

咱们能够看到,有七个参数
corePoolSize 线程池中常驻外围线程数
maximumPoolSize 线程池可能包容的同时执行的最大线程数,此值必须大于等于1
keppAliveTime 多余的闲暇线程的存活工夫
unit 存活工夫单位
workQueue 工作队列,被提交然而尚未执行的工作
threadFactory 示意生成线程池中工作线程的线程工厂,用于创立线程,个别默认即可
handler 回绝策略,示意队列满了并且工作线程大于等于线程池的最大线程数,如何解决。

后面几个参数都好了解,重要的是这个办法能够自定义阻塞队列的类型,咱们就能够防止应用LinkedBlockingQueue了

接下来,咱们看一下handler回绝策略:
当外围线程数满了,阻塞队列也满了的时候,再进来的线程,就会启动回绝策略,个别常见的回绝策略有这几种:

AbortPolicy(默认):间接抛出RejectExcutionException异样阻止线程运行。
CallerRunsPolicy:将多进去的工作退还给调用者,从而升高流量。

DiscardOldestPolicy:摈弃期待队列中期待最久的工作,而后把当前任务退出期待队列。

DiscardPolicy:间接抛弃工作

接下来,咱们尝试一下本人新建线程池,并尝试着应用该线程池执行一下工作。

     ThreadPoolExecutor threadPoolExecutor                = new ThreadPoolExecutor(2,//常驻外围线程数                5,//最大外围线程数                1L,//多余的闲暇线程的存活工夫                TimeUnit.SECONDS,//多余闲暇线程存活工夫单位                new ArrayBlockingQueue<Runnable>(10),//阻塞队列                Executors.defaultThreadFactory(),//生成线程池中工作线程的线程工厂                new ThreadPoolExecutor.AbortPolicy());//回绝策略        //模仿多个用户来办理业务,每一个用户就是来自内部的申请线程        try {            for (int i = 1; i < 10000; i++) {                threadPoolExecutor.submit(() -> {                    System.out.println(Thread.currentThread().getName() + "正在办理业务");                });            }        } catch (Exception e) {            e.printStackTrace();        } finally {            threadPoolExecutor.shutdown();        }    }

在应用以后回绝策略下,很显著能够看出,执行线程超出线程池的数量了,会抛异样,咱们来看一下运行后果:

咱们能够换一个回绝策略试一下:比如说DiscardPolicy(摈弃工作)

        ThreadPoolExecutor threadPoolExecutor                = new ThreadPoolExecutor(2,//常驻外围线程数                5,//最大外围线程数                1L,//多余的闲暇线程的存活工夫                TimeUnit.SECONDS,//多余闲暇线程存活工夫单位                new ArrayBlockingQueue<Runnable>(10),//阻塞队列                Executors.defaultThreadFactory(),//生成线程池中工作线程的线程工厂                new ThreadPoolExecutor.DiscardPolicy());//回绝策略


它就运行实现了,且没有抛出任何异样。

4.如何正当设置线程池参数
如何配置线程池的参数,其实咱们最须要配置的参数只有一个:
corePoolSize 线程池中常驻外围线程数

当咱们的线程池应用场景是在CPU密集型(过程绝大部份工作依附cpu的计算能力实现),那么为了避免上下文的切换造成的开销,咱们个别设置最大线程数等于CPU核数(核数获取办法:Runtime.getRuntime().availableProcessors();)

当咱们的线程池应用场景是在IO密集型(绝大部分工作就是在读入,输入数据),那么cpu不是十分十分忙碌,大部分在期待的状况下,最大线程能够是CPU核数的两倍。

5.总结
这次咱们介绍了线程池的原理以及参数详解,最重要的是,创立线程池必须要手动应用构造方法创立,不能应用默认的办法类进行创立!

最初,咱们用两张图来概括一下线程池的执行流程:

好了,明天的分享就到这里!