java多线程之-------阻塞队列-----线程池

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBLockingueue:一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBLockingQueue。
synchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高。

在这里插入图片描述
在这里插入图片描述
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒

为什么需要BlockingQueue

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
在这里插入图片描述
阻塞队列下常用的一共有七个
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
重点了解这三个标红的。线程池主要用这三个。

在这里插入图片描述
其中element是检查队首元素。
在这里插入图片描述
举例:

在这里插入图片描述在这里插入图片描述
SynchronousQueue没有容量。
与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。
每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

学习了阻塞队列,那么阻塞队列运用在哪些方面

生产者消费者模式

旧版的生产者消费者模式
Tips:多线程的判断只能用while,不能用if(API里边规定)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Synchronized和Lock有什么区别

1原始构成
synchronized是关键字属于JVM层面,
monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步块或方法中才能调wait /notify等方monitorexit
Lock是具体类(java.util.concurrent.Locks.Lock)是api层面的锁

2使用方法
synchronized 不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用ReentrantLock则需要用户去手动释放锁若没有主动降放锁,就有可能导致出现死锁现象。

3 等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
ReentrantLock可中断,
1.设置超时方法 tryLock(Long timeout,TimeUnit unit)
2.LockInterruptibly()放代码块中,调用interrupt()方法可中断

4 加锁是否公平
synchronized非公乎锁
ReentrantLock两者都可以,默认公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
5 锁绑定多个条件condition
synchronized没有
ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

锁绑定多个条件condition代码案例
本例完成这样一个任务,A线程打印5次然后通知B线程,B线程打印10次然后通知C线程打印15次,一共来10轮
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
另外两个方法类似于第一个方法,只需要修改一下每次通知的number
在这里插入图片描述
在这里插入图片描述
顺利执行
在这里插入图片描述
生产者消费者之阻塞队列版本
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
总体的大概是这样
在这里插入图片描述
这里加一个Boss叫停的方法;
在这里插入图片描述

然后main方法里的代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程池

TIps:
在这里插入图片描述
这个函数可以输出当前主机的CPU可用处理器

为什么用线程池,优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后后动这些任分,如果线程数量超过最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
他的主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

线程池如何使用?
在这里插入图片描述
之前我们使用的时候都有这样的辅助工具类
在这里插入图片描述
常用的有这三种:
Executors.newFixedThreadPool(int)-----固定线程池
用途:执行长期的任务,性能好很多
在这里插入图片描述
在这里插入图片描述

Executors.newSingleThreadExecutor()—单线程池
用途:一个任务一个任务执行的场景
在这里插入图片描述
在这里插入图片描述

Executors.newCachedThreadPool()–多线程池(缓存)
用途:执行很多短期异步的小程序或者负载较轻的服务器
在这里插入图片描述
如果不加这一句,那么在多线程的环境下会跳出十个线程同时执行,而加了那一句停留,那么有充分的时间一号线程就完全抢占了,这是这个线程池的策略。

在这里插入图片描述
在这里插入图片描述
线程池的几个重要参数介绍?
在这里插入图片描述

七大参数详解:

在这里插入图片描述
1.corePoolSize:线程池中的常驻核心线程数
1在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
2当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

2.maximumPoolSize:线程池能容纳同时执行的最大线程数,此值必须大于等于1

3.keepAliveTime:多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止

4.unit: keepAliveTime的单位。

5.workQueue:任务队列,被提交但尚未被执行的任务。

6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

7.handler: 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数( maximumPoolSize)时如何来n

在这里插入图片描述
在这里插入图片描述
1.在创建了线程池后,等待提交过来的任务请求。
2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这时候队列满了且正在运行的线和数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

说说线程池的底层工作原理

在这里插入图片描述
在这里插入图片描述
分析线程池的拒绝策略
等待队列也已经排满了,再也塞不下新任务了同时,
线程池中的max线程也达到了,无法继续为新任务服务。
这时候我们就需要拒绝策略机制合理的处理这个问题。

四种拒绝策略

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统止常运行。
CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
**DiscardOldestPolicy:**抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了RejectedExecutionHandler接口。
手写一个线程池
参数按照上面的银行办理业务为例:
在这里插入图片描述
在这里插入图片描述
最大线程数+阻塞队列数=8
在这里插入图片描述

当业务达到9个线程时:
在这里插入图片描述
就报了第一种异常:直接拒绝异常
在这里插入图片描述
当换了一种策略的时候,线程数达到10
在这里插入图片描述
可以看到多出来的线程回退到了调用者:
在这里插入图片描述
其他两种就不模拟了,是按照正常的策略返回结果。

如何合理配置线程池

CPU密集型

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),l
而在单核CPU上无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
CPU密集型任务配置尽可能少的线程数量:
一般公式:CPU核数+1个线程的线程池

IO密集型

1 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 CPU核数*2
2 Io密集型,即该任务需要大量的IO,即大量的阻塞。
在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
IO密集型时,大部分线程都阻塞,故需要多配置线程数:
参考公式:CPU核数/1-阻塞系数
阻塞系数在0.8~0.9之间
比如8核CPU:8/1-0.9=80个线程数

死锁编码及定位分析

在这里插入图片描述
产生死锁的原因:
1 系统资源不足 2 进程运行推进的顺序不合适 3 资源分配不当
解决:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/delete_bug/article/details/119183383