ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

线程池文章

2021-11-28 16:07:03  阅读:99  来源: 互联网

标签:协程 任务 线程 https 文章 执行 com


线程池

存在意义

  • 线程长连接,比如文件下载,网络请求不是很频繁,正常使用线程。
  • 线程短连接,频繁的读写,获取连接,关闭连接,对服务资源可能造成很大浪费,策略机制保障效能,所以有线程池。

Executors

  • 使用方式:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
    }
    
  • 参数说明:corePoolSize->workQueue->maximumPoolSize

    1. corePoolSize 核心线程数。
    2. workQueue 任务队列(无界队列、有界队列、同步移交队列),此队列仅保存实现Runnable接口的任务。
    3. maximumPoolSize 最大线程数。
    4. threadFactory 执行程序创建新线程时使用的工厂。
    5. handler 阻塞队列满且线程最大值时,采取的饱和策略。(AbortPolicy中止策略、DiscardPolicy抛弃策略、DiscardOldestPolicy抛弃旧任务策略、CallerRunsPolicy调用者运行)
    备注:
    AbortPolicy中止策略:抛出RejectedExecutionException(继承自RuntimeException),调用者可捕获该异常自行处理。
    
    DiscardPolicy抛弃策略:不做任何处理直接抛弃任务。
    
    DiscardOldestPolicy抛弃旧任务策略:先将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用优先级队列PriorityBlockingQueue,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用。
    
    CallerRunsPolicy调用者运行:既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行(把被拒绝的线程交给调用线程池的主线程执行)。使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。
    
  • 常用线程池类型:

    1. 在newCachedThreadPool,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    2. newFixedThreadPool,线程数量固定,使用无限大的队列。
    3. newScheduledThreadPool,定长线程池,支持定时及周期性任务执行。
    4. newSingleThreadExecutor,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    
  • 关系理解:Exectuor->ExectuorService->AbstractExectuorService->ThreadPoolExectuor

    一层一层不断扩展:
    1. ThreadPoolExecutor其实就是Executor的拓展,增加了一些新的控制方法。
    	 corePoolSize:线程池维护线程的最少数量
    	 maximumPoolSize:线程池维护线程的最大数量
    	 keepAliveTime:线程池维护线程所允许的空闲时间
    	 unit:线程池维护线程所允许的空闲时间的单位
    	 workQueue:线程池所使用的缓冲队列
    

实现分析

  • 线程池结构:

    1. 线程池(Thread pool),池是一个容器,容器中有很多个执行器,每一个执行器是一个线程。容器可以是链表、数组等。池需要提供一个取出执行器的方法、池中现有活动线程数的方法、销毁线程池的方法等。
    2. 执行器(Executor),每个执行器是一个线程,每个执行器可以执行一个任务。任务具体做什么,由诉求方定义。执行器应该有setter/getter方法,并且作为一个线程,可以独立运行。执行器执行完成后,需要将自身放入池中进行资源回收以再利用。
    3. 任务(Task),任务是每个线程具体要做的事,比如资源文件下载等,需要将自己交给执行器进行执行。
    
    同时,需要一个调度者(Scheduler)来协调主线程和池的关系。
    
  • 重要变量实现技巧:

    runState(线程池运行状态:运行还是关闭)和workerCount(线程池中的线程数目):
    
    1. private static int ctlOf(int rs, int wc) { return rs | wc; }
    2. private static int runStateOf(int c)     { return c & ~CAPACITY; }
    3. private static int workerCountOf(int c)  { return c & CAPACITY; }
    
    ctlOf是一个32位的数字,前三位存的是runState,后27位存的是workerCount。
    两个值存在同一个int变量中,取出来也只要通过位运算获取前3位和后27位数字即可。
    存在一起的目的是:不加锁实现线程安全。
    
  • 继承结构:

    Executor->ExecutorService->AbstractExecutorService->ThreadPoolExecutor->ScheduledThreadPoolExecutor
    Executor->ExecutorService   ->ScheduledExecutorService                ->ScheduledThreadPoolExecutor
    
  • runnable与thread的区别:

    1. Runnable为什么不可以直接run?
    runnable其实相对于一个task,并不具有线程的概念,如果你直接去调用runnable的run,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行。更进一步的,start是异步非阻塞调用,run是同步阻塞调用,多线程要求就是异步非阻塞调用,所以start才能体现多线程本质。
    
    2. Runnable和Thread相比优点有?
    由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以。
    Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread并不是不可以,而是相比于Runnable来说,不太适合。当以Thread方式去实现资源共享时,实际上源码内部是将thread向下转型为了Runnable,实际上内部依然是以Runnable形式去实现的资源共享。
    
  • execute与submit的区别:

    execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。threadsPool.execute(new Runnable() {  @Override  public void run() {  }});
    
  • 参数配置:

    ·任务的性质:CPU密集型任务、IO密集型任务和混合型任务。·任务的优先级:高、中和低。·任务的执行时间:长、中和短。·任务的依赖性:是否依赖其他系统资源,如数据库连接。CPU密集型任务:应配置尽可能小的线程,如配置N cpu + 1个线程的线程池。IO密集型任务:由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2 * N cpu。混合型的任务:如果可以拆分,将其拆分成一个CPU密集型任务 和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解,建议使用有界队列。I/O bound 指的是系统的CPU效能相对硬盘/内存的效能要好很多,此时,系统运作,大部分的状况是 CPU 在等 I/O (硬盘/内存) 的读/写,此时 CPU Loading 不高。CPU bound 指的是系统的 硬盘/内存 效能 相对 CPU 的效能 要好很多,此时,系统运作,大部分的状况是 CPU Loading 100%,CPU 要读/写 I/O (硬盘/内存),I/O在很短的时间就可以完成,而 CPU 还有许多运算要处理,CPU Loading 很高。
    
  • 线程组:

    Java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全。所以,线程组存在的意义,是安全方面考虑。
    
  • 线程池耗费资源吗?

    1. 每个线程占用多少内存?不同系统参数默认值不一样,同时不同用户也可以修改对应参数设定。真实情况,一个线程大概暂用内存100K左右。原因是虚拟内存和真实物理内存差异导致的。若 Xss 要求是 1M,那么每个线程会申请 1M 的虚拟内存,可是大部分线程并不会使用这么多,也就没必要占用这么多物理内存,使用多少个页(匿名 page),就提交多少个页。若按照每页 4K 计算,也就是平均25个页左右,就满足大部分线程的内存需求。默认的话,那个8M是进程栈,也是主线程的栈。8M对于99%的程序都是够用的,很少超过64k的,而且这个可ulimit重配置的。VSS:Virtual Size Size 虚拟耗用内存RSS:Resident Set Size 实际物理内存PSS:Proportional Set Size 实际使用的物理内存USS:Unique Set Size 进程独自占用的物理内存(最佳实用观察数据)2. 1个线程池开10个线程,与10个线程池每个开1个线程资源损耗是多少?额外开销应该差距不大。
    
  • 如何关闭线程池?

    使用shutdownNow方法,可能会引起报错,使用shutdown方法可能会导致线程关闭不了。所以当我们使用shutdownNow方法关闭线程池时,一定要对任务里进行异常捕获。使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。一定要记得,shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来阻塞等待。
    
  • 进程、线程和协程

    1. 协程,又称微线程,纤程。英文名Coroutine。一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。2. 进程资源分配的最小单位,线程是操作系统能够进行运算调度的最小单位。3. 线程是为了让操作系统并发运行程序,以达到”同时“运行更多程序的目的,而协程是为了让一个线程内的程序并发服务更多内容。(CPU资源划分层面并发、CPU时间片轮转层面并发、程序控制层面并发(站在程序视角并发))4. 线程切换是由操作系统的时间片控制的,而协程是程序自己实现的,让协程不断轮流执行才是实现并发,所以实现协程还必须要有一个类似于时间片的结构,不同于线程的切换,协程的切换不是按照时间来算的,而是按照代码既定分配,就是说代码运行到这一行才启动协程,协程是可以由我们程序员自己操控的。协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换。因此,协程的开销远远小于线程的开销。5. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:def A():print('1')print('2')print('3')def B():print('x')print('y')print('z')假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:12xy3z6. 看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?执行过程中挂起当前任务线程去执行其他任务线程,再回来执行原本任务线程。6.1 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。6.2 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。7. 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。Python对协程的支持是通过generator实现的。在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。8. 协程强调模块间的耦合,模块间必须相互熟悉,才知道谁在什么时候该让出时间片,谁在什么时候该抢占时间片。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。当下流行背后的诉求是使得线性书写多线程代码成为了可能。在所有线程相互独立且不会阻塞的模式下,抢断式的线程调度器是不错的选择。因为它可以保证所有的线程都可以被分到时间片不被程序员的垃圾代码所累。这对于某些事情来说是至关重要的,例如计时器、回调、IO触发器(譬如说处理请求)什么的。但是在线程不是相互独立,经常因为争抢而阻塞的情况下,抢断式的线程调度器就显得脱了裤子放屁了,既然你们只能一个个的跑,那抢断还有什么意义?让你们自己去让出时间片就好了。再往后,大家发现经常有阻塞的情况下,主动让出时间片的协程模式比抢占式分配的效率要好,也简单得多。9. 程序开发的一大矛盾是,你要用控制流去完成逻辑流。也就是说,你要用指令的执行来完成逻辑链条的前因后果。协程是一种任务调度机制,它可以让你用逻辑流的顺序去写控制流,而且还不会导致操作系统级的线程阻塞。你发起异步请求、注册回调/通知器、保存状态,挂起控制流、收到回调/通知、恢复状态、恢复控制流的所有过程都能过一个yield来默默完成。从代码结构上看,协程保证了编写过程中的思维连贯性,使得函数(闭包)体本身就无缝保持了程序状态。逻辑紧凑,可读性高,不易写出错的代码,可调试性强。10. 事实上协程和线程完全不是两个相同层面的东西,完全谈不上替代一说,协程可以说是一个独立于线程的功能,它是在线程的基础上,针对某些应用场景进一步发展出来的功能。我们知道,线程在多核的环境下是能做到真正意义上的并行执行的,注意,是并行,不是并发,而协程是为并发而生的。小结:协程是跨线程的并发。基于用户态的暂存,顺序逻辑描述并发控制,程序控制并发切换,资源占用少。线程涉及CPU内核态切换并发,是CPU自主自动控制,耗费CPU和内存资源。(协程更符合人们思维方式)
    
  • 信号量

    Semaphore(信号量)是java.util.concurrent下的一个工具类。用来控制可同时访问特定资源的线程数。内部是通过维护父类(AQS)的 int state值实现。Semaphore中有一个"许可"的概念:访问特定资源前,先使用acquire(1)获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。访问资源后,使用release()释放许可。应用场景:Semaphore用来控制访问某资源的线程数。
    
  • AQS

    CAS(原子) + volatile(有序、可见) + 自旋(策略,阻塞进行上下文切换开销很大,所以先自旋,不行再优化)-> 同步机制实现State(资源)+ CLH队列(FIFO)->排队等待资源
    

Reference

  • https://zhuanlan.zhihu.com/p/32867181(一次Java线程池误用引发的血案和总结)
  • https://www.jianshu.com/p/d58422800ac3(Executor与四种线程池)
  • https://www.cnblogs.com/kenshine/p/14520679.html(并发编程(十七):Excutor与ThreadPoolExcutor)图解不错!
  • https://github.com/alibaba/transmittable-thread-local(transmittable-thread-local)官方文档!
  • https://www.runoob.com/
  • https://blog.csdn.net/ljheee/article/details/55213272(自己实现JAVA线程池)
  • 《Java并发编程实战》
  • https://blog.51cto.com/u_15127574/3476174(自己实现一个线程池及分析java线程池源码)
  • https://blog.csdn.net/u011109589/article/details/80242931(Java锁–Lock实现原理(底层实现))
  • https://www.jianshu.com/p/333ce4b3d5b8(【Java面试】Runnable和Thread比较)
  • https://juejin.cn/post/6844904025213665288(ExecutorService+Callable+Future实现多线程控制并发数并返回数据)
  • https://www.cnblogs.com/wzj4858/p/8214717.html(多线程状态及线程池管理)图解不错!
  • https://blog.csdn.net/QQ578473688/article/details/54561907?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-4-54561907.pc_agg_new_rank&utm_term=java+%E7%BA%BF%E7%A8%8B%E7%BB%84%E6%9C%89%E4%BB%80%E4%B9%88%E7%94%A8&spm=1000.2123.3001.4430(Java 线程组和线程池区别)
  • https://www.jianshu.com/p/3bab26d25d2e(内存耗用:VSS/RSS/PSS/USS 的介绍)
  • https://mp.weixin.qq.com/s/wA3pUemz5oWJX6Zp9HFIGA(Java 的线程到底占用了多少内存?)
  • https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html(Java线程池实现原理及其在美团业务中的实践)
  • https://www.cnblogs.com/qingquanzi/p/9018627.html(如何优雅的关闭Java线程池)
  • https://zhuanlan.zhihu.com/p/148152129(尝试用通俗的方式解释协程)
  • https://www.jianshu.com/p/4a0d03da488e(协程理解)
  • http://www.wfuyu.com/mvc/21648.html(浅谈我对协程的理解)
  • https://blog.csdn.net/zheng199172/article/details/88800275(什么是协程)
  • https://www.zhihu.com/question/50185085/answer/1342613525(出于什么样的原因,诞生了「协程」这一概念?)
  • https://zhuanlan.zhihu.com/p/169426477(线程和协程的区别的通俗说明)
  • https://blog.csdn.net/c6E5UlI1N/article/details/119495236(协程到底是怎么切换线程的?)
  • https://zhuanlan.zhihu.com/p/27314456(深入理解信号量Semaphore)
  • https://segmentfault.com/a/1190000039024756(不能错过的CAS+volatile实现同步代码块)
  • https://www.cnblogs.com/waterystone/p/4920797.html(Java并发之AQS详解)

标签:协程,任务,线程,https,文章,执行,com
来源: https://blog.csdn.net/GZHarryAnonymous/article/details/121592824

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有