java 面试知识点笔记(十三)多线程与并发

Wesley13
• 阅读 1067

java线程池,利用Exceutors创建不同的线程池满足不同场景需求:

  1. newSingleThreadExecutor() 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  2. newFixedThreadPool(int nThreads) 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  3.  newCachedThreadPool() 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(默认60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
    1. 优点就是灵活创建线程,因地制宜,任务少时很省资源。缺点就是可创建的线程上限太大,源代码里是Integer.MAX_VALUE大小,这个数量有点可怕
  4. newScheduledThreadPool() 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
  5. newWorkStealingPool() jdk8引入的,内部会构建ForkJoinPool,利用working-stealing算法,并行的处理任务,但是不保证处理顺序

java 面试知识点笔记(十三)多线程与并发

Fork/Join框架

  • 把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果后得到大任务的框架
  • work-stealing算法:某个线程从其他线程队列里窃取任务来执行

java 面试知识点笔记(十三)多线程与并发

因为分割成若干个小任务由多个线程去执行,就会出现有的线程已经完成任务而有的还未完成任务,已经完成的线程就闲置了,为了提升效率,让已经完成任务的线程去其他线程窃取队列里的任务来执行。为了减少窃取线程对其他线程的竞争,通常会使用双端队列,执行任务的线程从头部拿任务执行,窃取线程是从队列尾部拿任务执行

问:为什么要使用线程池?

  1. 降低资源消耗(通过重复利用已创建的线程来工作,降低创建线程和销毁线程的消耗)
  2. 提高线程的可管理性(线程是稀缺资源,如果无限制的创建会不仅会消耗系统资源还会降低系统的稳定性,使用线程池可以统一的分配、调优、监控)

Executor的框架图

java 面试知识点笔记(十三)多线程与并发

JUC的三个Executor接口

  1. Executor:运行新任务的简单接口,将人物提交和任务执行细节解耦
    1. 通过源码看到newCachedThreadPool是返回的ExecutorService newSingleTreadExecutor是返回的FinalizableDelegatedExecutorService  最终都是继承的Executor
    2. java 面试知识点笔记(十三)多线程与并发
    3. java 面试知识点笔记(十三)多线程与并发
    4. java 面试知识点笔记(十三)多线程与并发
    5. java 面试知识点笔记(十三)多线程与并发
    6. java 面试知识点笔记(十三)多线程与并发
    7. java 面试知识点笔记(十三)多线程与并发
    8. java 面试知识点笔记(十三)多线程与并发
    9. java 面试知识点笔记(十三)多线程与并发
    10. 接口Executor只有一个方法就是execute,对于不同的实现它可能是创建一个新线程立即启动,也可能是使用已有的线程来运行传入的任务,也可能是根据线程池容量或阻塞队列的容量来决定是否将传入的任务放入阻塞队列中或者拒绝接受任务
  2. ExecutorService:具备管理执行器和任务生命周期方法,提交任务机制更完善
    1. ExecutorService是Executor的扩展接口 提供了更方便的管理方法 最常用的是 shutdown submit
    2. submit参数有Callable、Runnable两种 并返回Future
    3. java 面试知识点笔记(十三)多线程与并发
  3. ScheduledExecutorService:支持Future和定期执行任务

ThreadPoolExecutor的构造函数

  1. corePoolSize:核心线程数量
  2. maximunPoolSize:线程不够用时能够创建的最大线程数
  3. workQueue:任务等待队列(当前线程数量大于等于corePoolSize的时候,将任务封装成work放入workQueue中。不同的队列排队机制不同)
  4. keepAliveTime:线程池维护线程的空闲时间,线程空闲超过这个时间就会被销毁
  5. threadFactory:创建新线程,默认使用Executors.defaultThreadFactory(),新创建的线程是一样的优先级、非守护线程

ps:newCachedThreadPool传入的队列是容量为0的SynchronousQueue,(Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除操作take,反过来也一样)

handler:线程池的饱和策略

  1. AbortPolicy:直接抛出异常,这是默认策略
  2. CallerRunsPolicy: 用调用者所在多线程来执行任务
  3. DiscardOldestPolicy:丢弃队列中最靠前的任务,并执行当前任务
  4. DiscardPolicy:直接丢弃任务
  5. 实现RejectedExecutionHandler接口自定义handler处理

execute方法执行流程如下:

java 面试知识点笔记(十三)多线程与并发

线程池的状态:

  1. RUNNING:能够接受新任务,并且也能处理阻塞队列中的任务
  2. SHUTDOWN:不能接受新任务,但可以处理存量任务
  3. STOP:不再接受新任务,也不处理存量任务
  4. TIDYING:所有任务都已终止,正在进行最后的打扫工作,有效线程数为0
  5. TERMINATED:terminated()方法执行完成后进入该状态(该方法什么也不做只是标识)

状态转换图:

java 面试知识点笔记(十三)多线程与并发

工作线程的生命周期:

java 面试知识点笔记(十三)多线程与并发

问:如何选择线程池大小?(没有绝对的算法或规定,是靠经验累计总结出来的)

  • CPU密集型:线程数=按照核数或者核数+1(因为如果线程太多会导致过多的上下文切换,导致不必要的开销)
  • I/O密集型:线程数量=CPU核数*(1+平均等待时间/平均工作时间)

ps:

阿里编码规范指出:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

例子:使用Guava的ThreadFactoryBuilder 

java 面试知识点笔记(十三)多线程与并发

java 面试知识点笔记(十三)多线程与并发

输出:

java 面试知识点笔记(十三)多线程与并发

java 面试知识点笔记(十三)多线程与并发

不加重试的输出是:

java 面试知识点笔记(十三)多线程与并发

从例子中看出 maxPoolSize + QueueSize < taskNum 就会抛出拒绝异常 如果不catch这个异常程序无法结束(这里重试机制只是个demo,正确的做法是实现RejectedExecutionHandler接口自定义handler处理)

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java_线程池
血一样的教训,今天上午参加了一家现场面试java。在这之前,我一直认为我的java基础还是可以的,而今天一问三不知。现在将面试的问题整理出来一、说说java中的线程池?  1.线程池:线程池是线程的集合,不用自己创建线程,把线程直接给线程池,由线程池处理。   2.过程:首先,使用线程池可以重复利用已有的线程继续执行任务,避免线程在
Wesley13 Wesley13
3年前
Java多线程之线程池7大参数、底层工作原理、拒绝策略详解
Java多线程之线程池7大参数详解目录企业面试题线程池7大参数源码线程池7大参数详解底层工作原理详解线程池的4种拒绝策略理论简介面试的坑:线程池实际中使用哪一个?1\.企业面试题线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻塞队列用是什么?线程池的构造类的方
Stella981 Stella981
3年前
ExecutorService 线程池 (转发)
1.ExecutorServicejava.util.concurrent.ExecutorService接口。用来设置线程池并执行多线程任务。它有以下几个方法。Future<?java.util.concurrent.ExecutorService.submit(Runnabletask)提交任务并执行,返回代表这个任务的future
Wesley13 Wesley13
3年前
Java基础教程——线程池
启动新线程,需要和操作系统进行交互,成本比较高。使用线程池可以提高性能——线程池会提前创建大量的空闲线程,随时待命执行线程任务。在执行完了一个任务之后,线程会回到空闲状态,等待执行下一个任务。(这个任务,就是Runnable的run()方法,或Callable的call()方法)。Java5之前需要手动实现线程池,Java5之
Wesley13 Wesley13
3年前
Java 多线程,线程池,
1\.创建线程池的方法之三://对于每个任务,如果有空闲的线程可用,立即让他执行任务,//没有空闲的线程则创建一个线程。ExecutorServicepoolExecutors.newCachedThreadPool();//固定大小的线程池,任务数空闲线程数,得不到服务的任务
Stella981 Stella981
3年前
Noark入门之线程模型
0x00单线程多进程单线程与单进程多线程的目的都是想尽可能的利用CPU,减少CPU的空闲时间,特别是多核环境,今天咱不做深度解读,跳过...0x01线程池锁最早的一部分游戏服务器是采用线程池的方式来处理玩家的业务请求,以达最大限度的利用多核优势来提高处理业务能力。但线程池同时也带来了并发问题,为了解决同一玩家多个业务请求不被
Wesley13 Wesley13
3年前
Java中的线程池
java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理使用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺
ThreadPoolExecutor线程池内部处理浅析 | 京东物流技术团队
我们知道如果程序中并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束时,会因为频繁创建线程而大大降低系统的效率,因此出现了线程池的使用方式,它可以提前创建好线程来执行任务。本文主要通过java的ThreadPoolExecutor来查看线程池