线程池专题(上)

1.为什么要用线程池?

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间.
2.线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
2.执行execute()方法和submit()方法的区别是什么?

execute和submit都属于线程池的方法
execute只能提交Runnable类型的任务,无返回值
submit既能提交Runable类型的任务,返回值为null,也能提交Callable类型的任务,返回值为Future。

3.线程池的核心参数?

1. corePoolSize 线程池核心线程大小
2. maximumPoolSize 线程池最大线程数量
3. keepAliveTime 多余的空闲线程存活时间
4. unit 空闲线程存活时间单位
5. workQueue 工作队列
6. threadFactory 线程工厂:可以定制线程对象的创建,例如设置线程名字、是否是守护线程等。
7. handler 拒绝策略:当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略
       AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种)
       DiscardPolicy:丢弃任务,但是不抛出异常
       DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 。也就是当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务从队尾添加进去,等待执行。
     CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃
1
2
3
4
5
6
7
8
9
10
11
4.线程池执行任务的流程?

线程池的执行过程,可以分为三个主要步骤:

1.提交任务后会首先进行当前工作线程数与核心线程数的比较,如果当前工作线程数小于核心线程数,则直接调用 addWorker() 方法创建一个核心线程去执行任务;

2.如果工作线程数大于核心线程数,即线程池核心线程数已满,则新任务会被添加到阻塞队列中等待执行,当然,添加队列之前也会进行队列是否为空的判断;

3.如果线程池里面存活的线程数已经等于核心线程数了,且阻塞队列已经满了,再会去判断当前线程数是否已经达到最大线程数 maximumPoolSize,如果没有达到,则会调用 addWorker() 方法创建一个非核心线程去执行任务;

4.如果当前线程的数量已经达到了最大线程数时,当有新的任务提交过来时,会执行拒绝策略

总结来说就是优先核心线程、阻塞队列次之,最后非核心线程。

5.常用的java线程池的类型?

1 .newCachedThreadPool (缓存线程池)
2 .newFixedThreadPool(定长线程池)
3.newSingleThreadExecutor(单线程线程池)
4.newScheduleThreadPool(周期性任务定长线程池)

1 缓存线程池
(长度无限制)

执行流程:

判断线程池是否存在空闲线程
存在则使用
不存在,则创建线程 并放入线程池, 然后使用
2 定长线程池
(长度是指定的数值)

执行流程:

判断线程池是否存在空闲线程
存在则使用
不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
3 单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
执行流程:

判断线程池 的那个线程 是否空闲
空闲则使用
不空闲,则等待 池中的单个线程空闲后 使用
4 周期定长线程池
· 定时一次执行

· 周期性执行任务

6.线程池常用的阻塞队列

1.SynchronousQueue:对应的线程池是 CachedThreadPool,CachedThreadPool 是线程数可以无限扩展,所以 CachedThreadPool
线程池并不需要一个任务队列来存储任务,因为一旦有任务被提交就直接转发给线程或者创建新线程来执行,而不需要另外保存它们。
2…LinkedBlockingQueue:它的容量是 Integer.MAX_VALUE,为 231 -1 ,是一个非常大的值,可以认为是无界队列。

FixedThreadPool 和 SingleThreadExecutor
线程池的线程数是固定的,所以没有办法增加特别多的线程来处理任务,这时就需要 LinkedBlockingQueue
这样一个没有容量限制的阻塞队列来存放任务。
3…DelayedWorkQueue:它对应的线程池分别是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,这两种线程池的最大特点就是可以延迟执行任务,比如说一定时间后执行任务或是每隔一定的时间执行一次任务。

DelayedWorkQueue
的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构(堆的应用之一就是
优先级队列)。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择
DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。

7.如何合理配置线程池的参数
没标准配置,综合考虑(cpu密集型、io密集型)
1、CPU密集型

 CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。

   CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

   CPU密集型任务配置尽可能少的线程数。

   CPU密集型线程数配置公式:CPU核数+1
1
2
3
4
5
6
7
2、IO密集型
IO密集型,即该任务需要大量的IO,即大量的阻塞。

在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。

所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间

  第一种配置方式:

   由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。

   配置公式:CPU核数 * 2。

   第二种配置方式:

   IO密集型时,大部分线程都阻塞,故需要多配置线程数。

   配置公式:CPU核数 / 1 – 阻塞系数(0.8~0.9之间)

   比如:8核 / (1 – 0.9) = 80个线程数
1
2
3
4
5
6
7
8
9
10
11
12
13
8.excutor和executors的区别?
Executor, ExecutorService, 和 Executors 最主要的区别是 Executor 是一个抽象层面的核心接口
Executor vs ExecutorService vs Executors
总结:

Executor 和 ExecutorService 这两个接口主要的区别是:
1.ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口。
2.Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。
3.Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。

4.Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。

Executors 类提供工厂方法用来创建不同类型的线程池。**比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int
numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

9.源码中线程池是怎么复用线程的?
在线程池中,同一个线程去执行不同的任务,这就是线程复用。

线程的复用是通过while循环实现的,ThreadPoolExecutor中有个内置对象worker,每一个worker都是一个进程,worker会首先获取当前的firstTask进行run,然后不停的循环从等待队列中获取新的任务(task),如果有新任务则直接调用task的run方法,不会再去新建一个线程,从而实现复用

小结
线程池中的worker通过获取本身firstTask 或者 通过getTask方法 从等待队列中不停的循环去获取新的task,调用task的run方法执行任务,来实现线程的复用。

10.使用线程池可能带来哪些风险和哪些好处?

风险:
1.死锁
2.资源不足:线程消耗包括内存和其它系统资源在内的大量资源,还有就是虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
3.并发错误:线程池和其它排队机制依靠使用 wait() 和 notify() 方法,这两个方法都难于使用。如果编码不正确,那么可能丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理
4.线程泄漏:当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。

有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间
**5.请求过载:**过多请求压垮了服务器

好处:

1、线程池的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
2、控制线程池的并发数 :控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞
3、线程池可以对线程进行管理:线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
————————————————
版权声明:本文为CSDN博主「什么什么啊啊啊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_51711443/article/details/129239375

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值