java 线程池介绍

<span style="font-size:18px;">比如我有10个任务需要开起线程去服务器上下载东西,你可以写这样的代码:</span><span style="font-size: 18px;">
</span>

class MyTask implements Runnable{
		public void run() {
			
		}
	}
	public static void main(String[] args) {
		MyTask myTask = new MyTask();
		Thread thread = new Thread(myTask);
		thread.start();
	
	}

但是如果你考虑下效率或者在功能能完成的情况下想优化下代码的话,这里就要好好想想,这样用好不好,其实在我们做项目中,几乎不可能出现这种低效率的代码,不是说我技术多好,而且java给我们封装的太完成了,在java1.5后,jdk就给我们听过了线程池去操作类似这样的业务场景,因此为什么有线程池的由来,我们知道开启线程过多,会消耗cpu的资源,因为线程一多会去抢cpu执行的时间片,而且cpu来回切换执行线程也是费时间的,我们知道 单核cpu,同一时刻只能处理一个线程,多核cpu同一时刻可以处理多个线程,操作系统为每个运行线程安排一定的CPU时间----时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因为时间相当短,多个线程频繁地发生切换,因此给用户的感觉就是好像多个线程同时运行一样,但是如果计算机有多个CPU,线程就能真正意义上的,比如下面的图就很形象的表示了此意思,


既然说到了在开启线程过多的时候,建议使用线程池,那么线程池有什么好处和作用呢?

线程池的作用:

  • 线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。
  • 频繁创建和销毁线程耗资源,耗时间
  • 因为有的线程执行时间比创建和销毁一个线程的时间还长
这些多是一些概念性的东西,只有当我们真懂了,这些概念就自然在我们脑海中,既然java给我们封装了线程池,那么就讲下线程池基本的常用api

线程池涉及的类

  • Executor:Java里面线程池的顶级接口
  • ExecutorService:真正的线程池接口
  • ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
  • ThreadPoolExecutor(重点):ExecutorService的默认实现类。
  • ScheduledThreadPoolExecutor:继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 
它的类结构图如下:


通常我们创建线程池有二种方式,一种是通过Execuoors帮助类去创建线程池,还有一种就是java给我们提供了一个实现类,就是ThreadPollExector,一般都是用后者,因为更好控制和维护,

那么就讲下这二个类的基本用法:

Executors:jdk1.5之后的一个新类,提供了一些静态工厂,生成一些常用的线程池,ThreadPoolExecutor是Executors类的底层实现,他常用的方法如下:

1:.newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行>所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池>保证所有任务的执行顺序按照任务的提交顺序执行。

2:newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3:newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

我们重点是讲ThreadPoolExecutor,它的构造函数比较麻烦,形参比较多,现在就讲下它的形参都是什么意思,因为它会涉及到我们下面讲的一些概念和原理:

//构造方法
public ThreadPoolExecutor(int corePoolSize,//核心池的大小
                              int maximumPoolSize,//线程池最大线程数
                              long keepAliveTime,//保持时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler) //异常的捕捉器
现在对几个形参做下解释,如果画图理解,

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
workQueue : 任务队列,是一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,参考BlockingQueue

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
threadFactory : 线程工厂,如何去创建线程的

handler : 任务队列添加异常的捕捉器,参考 RejectedExecutionHandler

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

基础API的介绍

  • sShutdown() : 判断线程池是否关闭
  • isTerminated() : 判断线程池中任务是否执行完成
  • shutdown() : 调用后不再接收新任务,如果里面有任务,就执行完
  • shutdownNow() : 调用后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止之
  • submit() : 提交执行任务
  • execute() : 执行任务
上面一大片都是讲一些关于ThreadPoolExecutor类的一些使用以及他有的方法,
现在画图讲下ThreadPoolExecutor类构造函数的具体意思,其实也就是线程池处理的一些策略

1:如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建执行这个任务,这个可能听起来比较模糊,没关系,看下图


大的蓝色背景表示线程池,那么从中就可以看的出来,线程池包含工作线程和任务队列,关于任务队列是干嘛用的,可以理解为是一个集合里面维护了要执行的任务,等下会讲到,上面的黄色3个task,表示正在执行的任务,红色的task表示要加进来执行的任务,工作线程是5就是表示在线程池中还没执行任务时,线程池中就创建了5个空线程等待要执行的任务,其实就是corePoolSize,表示初始化创建了5个核心线程,假如红色task要执行,它会先查看工作线程,我们从图看的出来,线程池中正在执行的任务有3个还有2个线程是空闲的,这个时候如果红色的task要执行,它会先判断工作线程有没有空闲的线程给它执行任务,如果有,就交给空闲线程去执行,这是理想的状态下,就是工作线程数(这里说的工作线程是表示要执行的任务,比如上面3个淡黄色就表示是工作线程数)小于核心线程数,如果这个时候工作线程5个都在执行任务的话,这个时候任务队列就派上用场了,把要执行的任务缓存到任务队列中,如果工作的线程有空闲线程的话,马上就会用任务队列中取出刚才缓存的任务去执行,前面说了任务队列就是就是一些集合,有固定大小的,也有大小不固定的,如果这个时候固定大小的任务队列缓存满了任务的话,就会去创建额外的线程让刚添加进来的任务得以执行,那么这个时候工作线程就该+1了,如果任务队列中缓存的任务没满的话,而且这个时候额外线程有段时间(其实就是keepAliveTime的作用)没有任务给它执行的话,就要把这个额外的线程销毁,工作线程也就是核心线程还是回到刚才的个数,打个比方,比如杭州明年举办峰会,很多国家的领导人要来参加,那么安保就是一个大问题了,特别是有ISIS这种恐怖主义在的话,对安保要求肯定很多,但是杭州所有的警察加起来又不多,这个时候要临时请一些城管来帮忙了,当一些领导人回家了,安保就相对轻松了,那么这个时候可以把城管请走,因为城管要钱啊,很形象的一个比喻!其实就是这个意思!那么什么是最大线程数呢?其实就是当你添加任务很多时,而且任务队列和工作线程都满了,比如最大线程数是100个,当101个任务加进来的话,超过它的最大线程数,这个时候就不会再创建线程了,这个时候就需要我们构造函数中的最后一个形式,异常捕获了,就是告诉人家,我执行的任务满了,不能再执行新添加的任务了!刚才讲到了任务队列,java给我们几个常用的任务队列如下:

阻塞队列的介绍(BlockingQueue)

阻塞队列,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒,同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间时才会被唤醒继续操作。

  1. 基础API介绍

    • 往队列中加元素的方法

      • add(E) : 非阻塞方法, 把元素加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则抛出异常。
      • offer(E) : 非阻塞, 表示如果可能的话,将元素加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
      • put(E):阻塞方法, 把元素加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里有空间再继续。
    • 从队列中取元素的方法

      • poll(time): 阻塞方法,取走BlockingQueue里排在首位的元素,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
      • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。
  2. 子类介绍

    • ArrayBlockingQueue(有界队列): FIFO 队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小

    • LinkedBlockingQueue(无界队列):FIFO 队列,大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。

    • PriorityBlockingQueue:优先级队列, 类似于LinkedBlockingQueue,但队列中元素非 FIFO, 依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序

    • SynchronousQueue(直接提交策略): 交替队列,队列中操作时必须是先放进去,接着取出来,交替着去处理元素的添加和移除

RejectedExecutionHandler介绍

实现的子类介绍

  • ThreadPoolExecutor.AbortPolicy

    当添加任务出错时的策略捕获器,如果出现错误,则直接抛出异常

  • ThreadPoolExecutor.CallerRunsPolicy

    当添加任务出错时的策略捕获器,如果出现错误,直接执行加入的任务

  • ThreadPoolExecutor.DiscardOldestPolicy

    当添加任务出错时的策略捕获器,如果出现错误,移除第一个任务,执行加入的任务

  • ThreadPoolExecutor.DiscardPolicy

    当添加任务出错时的策略捕获器,如果出现错误,不做处理

ok,到此结束!



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值