题目 (这道题在互联网上已经有了)
1 2 3 可以添加任务,任务包含任务数据,任务延迟触发的等待时间。 在任务到达触发时间点时,自动触发执行此任务。 队列中任务保持先进先出原则:假设 A 任务的触发等待时间为 X,B 任务的触发等待时间为 Y,B 在 A 之后被添加入队列,则 A 的前驱任务执行完成后等待时间 X 后,才执行 A,同理在 A 执行完成后,等待时间 Y,才执行 B。
思路过程 1.Java上线 读题目就是延时队列 的特征,Java有锁,有多线程,写起来多方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 import java.util.concurrent.BlockingQueue;import java.util.concurrent.DelayQueue;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public class HandWritingQueue { public static void main (String[] args) { final BlockingQueue<DelayedElement> deque = new DelayQueue <>(); Runnable producerRunnable = new Runnable () { int i = 10 ; public void run () { while (true && i>0 ) { try { --i; System.out.println("producing " +i+",wait " +i+" seconds" ); deque.put(new DelayedElement (1000 * i, "i=" + i)); Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Runnable customerRunnable = new Runnable () { public void run () { while (true ) { try { System.out.println("consuming:" + deque.take().msg); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Runnable getSize= new Runnable () { @Override public void run () { while (true ) { System.out.println("size=" +deque.size()); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread1 = new Thread (producerRunnable); thread1.start(); Thread thread2 = new Thread (customerRunnable); thread2.start(); Thread thread3 = new Thread (getSize); thread3.start(); } static class DelayedElement implements Delayed { private final long expire; private final String msg; public DelayedElement (long delay, String msg) { this .msg = msg; expire = System.currentTimeMillis() + delay; } @Override public long getDelay (TimeUnit unit) { return unit.convert(this .expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo (Delayed o) { return -1 ; } } }
2.Node上线 被提醒该题目可以用node实现,且不需要借助redis来做,然后我上手就是一把操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 'use strict' class DelayElement { constructor (data, expire ) { this .data = data; this .expire = expire; } } const delayArray = [];delayArray.push (new DelayElement (1 , 2 )); delayArray.push (new DelayElement (2 , 1 )); let length = delayArray.length ;let time_cnt = 0 ;while (delayArray.length > 0 ) { let de = delayArray.shift (); time_cnt += de.expire ; (function ( ) { setTimeout (() => { console .log ('expire data is :' + de.data + ',expire time is :' + de.expire ); }, time_cnt * 1000 ); })(); }
我以为设计的考点也就是立即执行函数,延时的使用,但是这里的for循环是个伪串行,实际上是并发的,也为第三步的修改提供了bug
3.Promise时代 一开始我是想把async函数放进去,写了如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 'use strict' const delayArray = [];const daPush = (data, expire ) => { delayArray.push (async () => { setTimeout (() => { console .log ('data is ' + data + ' and expire is ' + expire); }, expire * 1000 ); }); } daPush (1 , 4 );daPush (2 , 5 );(async () => { for (const da of delayArray) { await da (); } })();
发现代码还是串行的,然后查了一下可能的问题(以下为个人猜测,欢迎指正)async声明的函数会包装成Promise不假,但是for循环会并发去执行await中的async
4.正解
promise执行会阻塞主线程
Macrotasks和Microtasks 都属于上述的异步任务中的一种,他们分别有如下API:macrotasks: setTimeout, setInterval, setImmediate, I/O, UI renderingmicrotasks: process.nextTick, Promise, MutationObserver
任务队列中,在每一次事件循环中,macrotask 只会提取一个执行 ,而microtask 会一直 提取,直到microsoft队列为空 为止。
也就是说如果某个microtask任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。
而事件循环每次只会入栈一个macrotask,主线程 执行完成该任务后又会检查microtasks 队列并完成里面的所有任务后再执行macrotask 的任务。
以及macrotask应该对应的是check队列(该行未验证)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 'use strict' const delayArray = [];const daPush = (data, expire ) => { delayArray.push (() => new Promise ((resolve,reject ) => { setTimeout (() => { if (data) { console .log ('data is ' + data + ' and expire is ' + expire); resolve (true ); } else { reject ('there is nodata' ); } }, expire * 1000 ); })); }; daPush (1 , 4 );daPush (2 , 5 );(async () => { for (const da of delayArray) { da ().then ((value )=> { }).catch ((value )=> { console .log (value); }); } })();