如何应对Android面试官->阻塞队列和线程池原理,手写自动收货系统核心实现
作者:访客发布时间:2023-12-22分类:程序开发学习浏览:126
阻塞队列
队列
一种数据结构,先进先出;
阻塞队列
队列满了,不能放入新的数据,这个放的操作就是阻塞队列;
队列空的,想要从队列中取出数据,这个取的操作就是阻塞队列;
阻塞队列专用接口:BlockingQueue
阻塞队列中不仅仅是有阻塞方法,还有非阻塞方法;
阻塞队列中的接口一般都是成对儿出现的,例如 add/remove offer/poll take/put
add/remove 是非阻塞的,但是往一个满的队列添加数据的时候会抛出一个异常,往一个空的队列删除数据的时候会抛出一个异常;
offer/poll 是非阻塞的,但是往一个满的队列添加数据的时候会返回一个 false,往一个空的队列删除数据的时候会返回一个 null;
take/put 是阻塞的;
阻塞队列常用来解决什么问题?
通常用来解决生产者与消费者模式;生产者将产物交给队列,消费者从队列中获取,无需关心产物来自哪里,解耦生产者和消费者;
一种是生产者速度大于消费者速度,一种是消费者速度大于生产者速度,为了平衡生产者和消费者之间的这种关系,中间添加了一层容器,生产者只管将生产的内容放入容器,消费者只管从容器中获取数据内容,无需关心内容从哪里来的;
常用的阻塞队列
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列;
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列;
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列;
DelayQueue:一个使用优先级队列实现的无界阻塞队列;
SynchronousQueue:一个不存储元素的阻塞队列;
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列;
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列;
**有界:**队列长度是有限制的,满了以后生产者就会被阻塞(规定了最大容量);
**无界:**可以不停的往里面放东西,不会被阻塞(没有规定最大容量);
构造方法中指明了最大容量;
构造方法中指明的是初始容量,可以无限的往里面添加内容;
DelayQueue:支持元素的延迟获取,DelayQueue 对泛型进行了约束,它是 Delayed 的子类,而 Delayed 又是 Comparable 的子类;Comparable 用来做比较,getDelay() 返回当前元素当前实测的一个剩余时长,也就是说当我们往一个 DelayQueue 中放入一个元素的时候,比较的是当前元素的一个剩余时长,谁的时间短,谁就排在队列的前面,最先被取出来,同时 DelayQueue 在阻塞的情况下还比 BlockingQueue 多了一个约束,什么约束呢?就是说:当你从 DelayQueue 中取元素的时候,如果元素剩余时间还没到,是获取不到元素的。必须要等到时间到了才能获取到,通常用来设计单机系统;
SynchronousQueue:当我们使用生产者线程往 SynchronousQueue 放入元素的时候,就必须同时一个消费者线程在对端使用 take 方法将这个元素取走,否则生产者是不能继续往里面添加元素的,常用于数据的直接传递;
LinkedTransferQueue:比其他队列多了一个 transfer 方法,尝试传输,当生产者把元素通过队列传给消费者的时候,它发现正好有一个消费者在等待着获取元素的时候,那么它就会把这个元素直接交给消费者,不再往队列里面放,但是这里有一个约束:如果没有消费者来接收这个元素,那么 transfer 就会阻塞在那里,直到有消费者接收这个元素之后,transfer 才会返回;而 tryTransfer 会先进行试探,试探这个元素有没有消费者接收,没有消费者接收,就会把这个元素放入容器里,tryTransfer 马上就会结束;
LinkedBlockingDeque:一般的队列都是一个入口,一个出口,而 LinkedBlockingDeque 队列可以同时作为出口和入口;
线程池
什么是线程池?
为了节省线程的创建和销毁时间,缩短任务总的执行时间,准备一批线程放在那里统一管理分配使用,而存放这批线程的空间,就叫线程池;
为什么需要线程池?
new Thread,创建线程,它会消耗操作系统资源,不管是创建线程还是销毁线程都是有资源消耗的;
执行一个线程需要的时间:T1、创建线程时间;T2、任务执行时间;T3、线程销毁时间;
所以线程是稀缺昂贵的资源;
如何创建线程池,各个参数的含义?
- corePoolSize
- 当前线程池的核心线程数;
- int maximumPoolSize
- 当前线程池的最大线程数,表示当前线程池所能够使用的最大线程数量;
- long keepAliveTime
- 存活时间;用来控制空闲的线程的存活时间,如果超过了存活时间,线程就会被销毁;
- TimeUnit unit
- 存活时间单位 s/ms;
- BlockingQueue workQueue
-
缺省阻塞队列实现;
-
当任务数量大于线程数量的时候,超过的任务数放到阻塞队列中,例如:maximumPoolSize 设置的 100,但是我们往线程池中提交了 1000 个线程,那么剩余的 900 个就会放入这个阻塞队列中;
-
尽量配置成有界的阻塞队列,无界的可能会撑爆机器;
- ThreadFactory threadFactory
- 线程创建的时候,可以允许我们对线程做一点点微调整工作,通常给线程定一个名字;
- RejectedExecutionHandler handler
-
拒绝策略,对超出线程池能力的任务提出的一种拒绝策略;
-
假设最大线程数和阻塞队列一共可以装载 2000 个,但是提交了 2001 个任务,不能处理了,就需要这个拒绝策略来拒绝(怎么处理超出能力之外的任务);
-
缺省拒绝策略;
-
DiscardOldestPolicy:直接丢弃最老的任务,排在队列最前面的扔掉,执行当前任务;
-
AbortPolicy:直接抛出异常;
-
CallerRunsPolicy:让调用者线程执行任务,哪个线程往线程池提交任务,就由谁来执行(you can you up);
-
DiscardPolicy:把最新提交的任务直接抛弃;
任务提交
- execute():无返回结果;
- submit():有返回结果,当需要关心返回结果的时候使用此方法;
任务中断
- shutdown(): 尝试关闭一个线程池,把所有没有执行任务的线程进行一个中断;
- shutdownNow():不管当前线程有没有执行,都尝试进行中断(不一定会成功,线程中断是一种协作机制,完全取决于有没有良好的处理中断信号);
根据任务类型,如何合理配置参数?
- CPU密集型任务
-
频繁从内存中取数据计算的任务就是 CPU 密集型任务;
-
maximumPoolSize 的值不要超过机器的 CPU 核心数;
-
通过接口 Runtime.getRuntime().availableProcessors() 来获取 CPU 的核心数,顶多加个1(因为机器的内存有限,操作系统会把磁盘一部分划分出来作为虚拟内存,+1 是为了防止页缺失状态);
-
页缺失状态:操作系统划分为真实内存和磁盘,但是会在磁盘上开辟一块空间作为虚拟内存,线程执行的数据一部分放在真实内存中,一部分可能放在虚拟内存中,如果数据放在虚拟内存中,那么操作系统不得不把这些数据调度到内存中,一旦发生这种情况,那么操作系统就会让线程进入一种叫做页缺失的状态(等到数据被调度到内存之后,再把线程唤醒,让线程继续工作);
- IO密集型任务
- 网络通信,读写磁盘的任务就是 IO 密集型任务;
- maximumPoolSize 的值一般是 CPU 核心数的 2 倍;
- 混合密集型任务
-
既有 CPU 密集型 又有 IO 密集型就是混合型任务;
-
如果两个任务耗时差不多的话,就考虑拆分成两个线程池;
-
如果两个任务耗时相差很大,哪个大设置哪个类型,例如 CPU 耗时 10ms,IO 耗时5s,那么就设置成 IO 密集型;
任务执行顺序(线程池工作机制)
-
提交任务,先交给 corePoolSize 中的线程执行;
-
corePoolSize 满了之后,交给 BlockingQueue 队列;
-
BlockingQueue 队列满了之后才交给最大线程数 maximumPool 中的线程;
-
最大线程数满了之后,交给拒绝策略进行拒绝处理;
整体流程图如下:
针对 IO 密集型任务,这里额外涉及到的两个知识点:DMA 机制、零拷贝技术;
DMA机制(IO操作不使用CPU)
CPU 在操作 IO 设备的时候,它不会自己去读写,而是向磁盘控制器,网络控制器发送一个信号「你要做什么事情,做完之后通知我」磁盘控制器或者网络控制器做完之后就会给 CPU 发出一个中断信号「硬件含义的中断」意思是「我处理完了,请你 CPU 处理」;这个过程 CPU 完全不参与,全部交给磁盘控制器来操作;
零拷贝
现代 OS 提出了两个空间概念:内核空间和用户空间,内核空间共享,用户空间独立,用户空间不能直接访问内核空间,只能通过接口( Binder 机制),例如:用户空间想访问内核空间网络硬件接口,只能通过内核空间提供的接口,因为用户空间不能直接访问网卡,需要将网卡提供的数据交给内核空间,内核空间再把数据拷贝到用户空间,这个交替的过程没有任何修改,就是一个简简单单的数据拷贝。当用户空间修改完数据之后,还得拷贝到内核空间交给网卡,那么这么一次网络通信发生了至少两次的拷贝,毫无意义,所以提出了零拷贝技术「OS 允许用户空间在内核空间单独申请一个空间,并且允许用户空间可以直接访问这块空间,那么所有的交互都直接通过这块空间进行,无需拷贝」;
线程池中的线程如何被复用?
线程执行了 run 方法会终止,抛出了异常会终止,那么就让当前线程的 run 方法执行不完,让当前线程在阻塞队列上 take,被阻塞;也就是当前线程调用阻塞队列的 take 方法使其阻塞起来,有任务 put 进来了再继续执行;
订单自动收货系统核心实现(队列 + 线程池)
我们在某宝购物之后,当订单收货之后,如果一段时间没有点击收货,那么就会自动收货,核心实现如下:
自动收货系统可以使用 DelayQueue 来实现,通过两个线程,一个是放需要自动收货的线程,一个是取需要自动收货的线程;
订单数据bean
public class OrderItem {
private final String orderNum;
private final double orderMoney;
public OrderItem(String orderNum, double orderMoney) {
this.orderNum = orderNum;
this.orderMoney = orderMoney;
}
public String getOrderNum () {
return orderNum;
}
public double getOrderMoney() {
return orderMoney;
}
}
订单过期和排序
使用 DelayQueue 需要实现 Delayed 接口,用来获取过期时间以及过期时间的排序
public class Item<T> implements Delayed {
// 到期时间
private long activeTime;
// 订单
private T data;
public Item(long expireTime, T data) {
this.activeTime = expireTime * 1000 + System.currentTimeMillis();
this.data = data;
}
public long getActiveTime () {
return activeTime;
}
public T getData() {
return data;
}
// 返回到激活日期的剩余时间
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.activeTime - System.currentTimeMillis(), unit);
}
@Override
public int compareTo(Delayed o) {
long d = (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
if (d == 0) {
return 0;
} else {
if (d < 0) {
return -1;
} else {
return 1;
}
}
}
}
存放自动收货订单线程
public class PutOrderItem implements Runnable{
private DelayQueue<Item<OrderItem>> putQueue;
public PutOrderItem(DelayQueue<Item<OrderItem>> queue) {
this.putQueue = queue;
}
@Override
public void run() {
OrderItem orderItem = new OrderItem("PDD12345", 400);
Item<OrderItem> item = new Item<>(5, orderItem);
putQueue.offer(item);
System.out.println("订单5秒后超时: " + orderItem.getOrderNum() + "" + orderItem.getOrderMoney());
OrderItem orderItem1 = new OrderItem("PDD54321", 500);
Item<OrderItem> item1 = new Item<>(8, orderItem1);
putQueue.offer(item1);
System.out.println("订单8秒后超时: " + orderItem1.getOrderNum() + "" + orderItem1.getOrderMoney());
}
}
获取自动收货订单线程
public class FetchOrderItem implements Runnable {
private DelayQueue<Item<OrderItem>> fetchQueue;
public FetchOrderItem(DelayQueue<Item<OrderItem>> queue) {
this.fetchQueue = queue;
}
@Override
public void run() {
while (true) {
try {
Item<OrderItem> item = fetchQueue.take();
OrderItem data = item.getData();
System.out.println("获取到的需要自动收货的订单:" + data.getOrderNum() + ", " + data.getOrderMoney());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
启动线程
public class TestDelayQueue {
public static void main(String[] args) throws InterruptedException {
DelayQueue<Item<OrderItem>> queue = new DelayQueue<>();
// 使用线程池
PutOrderItem putOrderItem = new PutOrderItem(queue);
// Thread putThread = new Thread(putOrderItem);
// putThread.start();
FetchOrderItem fetchOrderItem = new FetchOrderItem(queue);
// Thread fetchThread = new Thread(fetchOrderItem);
// fetchThread.start();
ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardOldestPolicy())
// ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.execute(putOrderItem);
threadPool.execute(fetchOrderItem);
// 这里用来打印当前进度
for (int i = 0; i < 20; i++) {
Thread.sleep(500);
System.out.println("i*500 = " + i*500);
} }
}
简历润色
简历上可写:深度理解阻塞队列和线程池原理,并熟练运用,可手写自动收货系统核心实现;
下一章预告
带你玩转 AQS 和 volatile
相关推荐
- 如何应对Android面试官->嵌套滚动原理大揭秘,实战京东首页二级联动
- 源码解析之OkHttp五大拦截器原理解析
- 如何应对Android面试官->布局原理与xml解析,手写插件化换肤框架核心实现(下)
- 全栈加持,让面试官小抄再次进化!
- 如何应对Android面试官->布局原理与xml解析,手写插件化换肤框架核心实现(上)
- 深入理解RecyclerView:布局管理器实现原理和使用方法
- 为什么做app开发岗位的面试官时我很少面算法题?
- Android 线程死锁场景与优化
- 如何应对Android面试官->手撸一个京东流式布局,MeasureSpec&LayoutParams 大揭秘
- 如何应对Android面试官->ART和Dalvik概论
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版