常常在系统里面都会有一些工作运行起来特别花时间,比如说寄送电邮,或是写入复杂数据到数据库中等等。在一般系统中如果没有特殊处理的话,会造成用户在使用的时候感觉很慢才有响应,而响应其实只是一个简单的运行完成的确认。
这时候异步处理这个好用的技巧就派上用场了,如果我们 能够让用户运行这个动作的时候,不用真的等到他完成再return回去给用户,其实只要被调用的程序确认已经收到了指令跟参数,就可以响应了,而实际 上运行工作的任务就交给另一个thread来处理即可。
要怎么做到这点呢?笔者过去看过了不少有趣的解决方式,常看到一些数据库的爱用者的作法,就是开一个table,然后把一笔一笔要运行的指令内容塞到这个table,然后由另一个thread来定期扫这个table未运行过的指令, 找到还没运行的指令就把他运行。这也是一种解决方式啦,只是感觉为了运行指令还要维护一个table麻烦了点,而随时去扫瞄table效能也差了一点。
比较聪明一点的开发者就会想了,那我们在内存里面作一个queue来记录包成物件的要运行的指令就好啦,就不必写在数据库里面。但是运行方式也是一样,我们开一个thread每一定时间运行一次来扫这个queue运行还没运行过的工作。这样效能是不差,但是还是没办法达到趋近于及时的效果,也就是说至少都还是得等一定时间系统才会来运行这样的工作。
其实有一个更简单的解法,这样的queue通常根本不必自己写,有一种东西叫做 Blocking Queue,也就是说他是一个会把thread lock住的一个queue,什么时候会lock住呢?就是在这个queue东西被清光的时候。
也就是说,这个queue的使用方式很简单,我们要运行的工作包成物件跟之前一样就把他丢到这个Blocking Queue里面,而另外我们起一个thread在一个循环里面不断的从Blocking Queue中获取下一个要运行的工作,如果queue里面已经没有工作可以给这个thread作了,这个thread就会被queue停住,直到有下一个工作丢入queue中,才会把thread叫醒,继续运行工作。Java文档中有的简单用法大概如下:
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
class Setup {
void main() {
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
这样的blocking queue其实自己实做起来还算满简单的,笔者当初想到这种解法的时候便自己implement了一个Blocking Queue来达到这样目的,不过一阵子后就发现Java 1.5以后,standard library里面就给了一个Blocking Queue来给你使用。就更省事更不会出错啦!