资讯专栏INFORMATION COLUMN

java并发编程学习3--线程池

anyway / 1507人阅读

摘要:线程池为了节省系统在多线程并发时不断创建和销毁线程带来的额外开销,就需要引入线程池。其中表示一个线程池。表示一个线程工厂,通过可以取得一个特定功能的线程池。创建固定数目线程的线程池。默认情况下,在创建了线程池后,线程池中的线程数为。

【线程池

为了节省系统在多线程并发时不断创建和销毁线程带来的额外开销,就需要引入线程池。线程池的基本功能就是进行线程的复用。当系统接受一个提交的任务时,并不会着急去创建一个新的线程去执行这个任务,而是去线程池中查询是否有空闲的线程。

若有:直接使用这个线程。

若没有:根据配置的策略执行(有可能时创建一个新的线程,也有可能是阻塞该任务等待空闲线程)。待任务结束之后,也不会销毁线程,而是放入线程池的空闲队列,等待下次使用。

【executor框架

为了能更好的控制多线程,jdk提供了一套executor框架。其中ThreadPoolExecutor表示一个线程池。Executors表示一个线程工厂,通过Executors可以取得一个特定功能的线程池。

public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。

public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

但是这些工厂方法最后都是使用的ThreadPoolExecutor这个类。

【ThreadPoolExecutor参数含义
        - corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用prestartAllCoreThreads()或者prestartCoreThread()方法。从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0。当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
       - maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
       - keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用。直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。 但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
       - unit:参数keepAliveTime的时间单位
       - workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
            ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;
       - threadFactory:线程工厂,主要用来创建线程;
       - handler:表示当拒绝处理任务时的策略,有以下四种取值:
           - ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出    RejectedExecutionException异常。
           - ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
           - ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)                      
           - ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

【现在重点讲讲workQueue

参数workQueue是指被提交但未执行的任务队列,他是一个BlockingQueue接口的对象,仅用于存放runnable对象。根据队列功能分类,在ThreadPoolExecutor构造参数中可以使用以下几种BlockingQueue:

- 直接提交队列:SynchronousQueue对象提供。SynchronousQueue是一个特殊的BlockingQueue,SynchronousQueue没有容量,不保存任务他总是将任务提交给线程执行,如果没有空闲的线程就会尝试创建新的线程。如果线程数达到maximumPoolSize,则执行拒绝策略。因此使用SynchronousQueue通常会设置很大的maximumPoolSize,否则容易执行拒绝策略。

- 有界任务队列:有界任务队列可以使用ArrayBlockingQueue实现,其构造函数必须带一个参数表示最大容量。当使用有界任务队列的时候:如果有新任务提交,若线程数小于corePoolSize,则会优先创建新的线程,若大于corePoolSize则会将任务置于等待队列中,如果等待队列已满,在线程数小于maximumPoolSize时,会创建新的线程执行任务,否则执行拒绝策略。最终,除非系统非常繁忙,否则线程数将会维持在corePoolSize。

- 无界任务队列:无界任务队列可以通过LinkedBlockingQueue类实现。与有界队列相比,除非系统资源耗尽,否则无界任务队列不存在任务入队失败的情况。当有新任务提交的时候:如果系统线程小于corePoolSize,会创建新的线程执行任务,如果大于corePoolSize则不会增加新的线程,任务会进入等待队列,无界队列会持续增长,直到系统资源耗尽。

- 优先任务队列:带有执行优先级的队列,通过PriorityBlockingQueue实现,可以控制任务执行顺序特殊的无界队列。无论有界还是无界队列,都是fifo的执行任务,但是优先级任务队列可以根据任务自身的优先级决定任务执行顺序,在确保了性能的同时,也能保证执行质量(高优先级任务先执行)。
【线程池如何复用线程

我们知道线程池会复用线程,但是它的内部逻辑是如何将一个Runnable对象赋值给Thread的呢?

1.线程池内部维护的不是Thread对象而是一个内部类Worker:

它继承了AbstractQueuedSynchronizer类,实现了一个非重入的锁。该锁会保护一个正在等待任务被执行的Worker不被interrupt操作打断。为什么不用ReentrantLock,要用非重入的锁?因为作者不想让这个Worker task在setCorePoolSize这种线程池控制方法调用时能重新获取到锁。当提交一个任务时,如果需要创建一个线程(何时需要在下一节中探讨)时,就调用线程工厂创建一个线程,同时将线程绑定到Worker工作队列中。需要说明的是,Worker队列构造的时候带着一个任务Runnable,因此Worker创建时总是绑定着一个待执行任务。换句话说,创建线程的前提是有必要创建线程,不会无缘无故创建一堆空闲线程等着任务。这是节省资源的一种方式。

2.线程重用:线程重用的核心是,它把Thread.start()给屏蔽起来了(一定不要重复调用),然后它自己有一个Runnable.run(),循环在跑,跑的过程中不断检查我们是否有新加入的子Runnable对象,有就调一下我们的run(),其实就一个大run()把其它小run()#1,run()#2,...给串联起来了,基本原理就这么简单。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68072.html

相关文章

  • java并发编程学习1--基础知识

    摘要:死亡状态线程退出有可能是正常执行完成也有可能遇见异常退出。类有新建与死亡状态返回其余状态返回判断线程是否存活。线程因某些原因进入阻塞状态。执行同步代码块的过程中执行了当前线程放弃开始睡眠进入就绪状态但是不会释放锁。 【java内存模型简介 JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个...

    huangjinnan 评论0 收藏0
  • 线程编程完全指南

    摘要:在这个范围广大的并发技术领域当中多线程编程可以说是基础和核心,大多数抽象并发问题的构思与解决都是基于多线程模型来进行的。一般来说,多线程程序会面临三类问题正确性问题效率问题死锁问题。 多线程编程或者说范围更大的并发编程是一种非常复杂且容易出错的编程方式,但是我们为什么还要冒着风险艰辛地学习各种多线程编程技术、解决各种并发问题呢? 因为并发是整个分布式集群的基础,通过分布式集群不仅可以大...

    mengera88 评论0 收藏0
  • java并发编程学习2--Future

    摘要:一个线程池包含很多准备运行的空闲线程,每当执行完毕后,线程不会死亡而是回到线程池准备为下一个请求提供服务。另一个使用线程池的理由是减少并发线程数。创建大量线程会大大降低性能甚至拖垮虚拟机。 【Future的概念 interface Future ,表示异步计算的结果,Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常...

    weizx 评论0 收藏0
  • 并发

    摘要:表示的是两个,当其中任意一个计算完并发编程之是线程安全并且高效的,在并发编程中经常可见它的使用,在开始分析它的高并发实现机制前,先讲讲废话,看看它是如何被引入的。电商秒杀和抢购,是两个比较典型的互联网高并发场景。 干货:深度剖析分布式搜索引擎设计 分布式,高可用,和机器学习一样,最近几年被提及得最多的名词,听名字多牛逼,来,我们一步一步来击破前两个名词,今天我们首先来说说分布式。 探究...

    supernavy 评论0 收藏0
  • 并发

    摘要:表示的是两个,当其中任意一个计算完并发编程之是线程安全并且高效的,在并发编程中经常可见它的使用,在开始分析它的高并发实现机制前,先讲讲废话,看看它是如何被引入的。电商秒杀和抢购,是两个比较典型的互联网高并发场景。 干货:深度剖析分布式搜索引擎设计 分布式,高可用,和机器学习一样,最近几年被提及得最多的名词,听名字多牛逼,来,我们一步一步来击破前两个名词,今天我们首先来说说分布式。 探究...

    ddongjian0000 评论0 收藏0

发表评论

0条评论

anyway

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<