多线程 & 异步调用 的理解
最近做项目,高并发的情况比较常见,因此常常需要用到多线程。而之前一直对多线程处于一个比较模糊的状态,这次终于清晰了点儿。其实理解多线程可以和异步调用结合起来理解会比较好。
对于同步调用和异步调用,可以用以下伪代码来粗略的看一下:
同步调用:
public void test() { //某段代码 //这里是入db的操作 this.saveDataToDb(Map params); }
异步调用:
public void test() { //某段代码 //这里是入db的操作 new Thread(new SaveDataToDb(Map param)).start(); }
其中SaveDataToDb类是一个实现了Runnable接口的线程。
在同步调用中,你必须等待“某段代码”执行完毕后,再执行saveDataToDb操作。而入db是一个相对耗时的操作。在非并发的情形下,这样做性能上的劣势还可能不能体现出来。但当是高并发的情况和不定时被执行的情况下呢?我们总不能执行到saveDataToDb处时,就一直等待saveDataToDb执行完,才开始接收下一次的任务请求吧!所以就必须开启另一个线程。
于是就用到了异步调用。把saveDataToDb的逻辑放到一个线程类的run方法里,而当执行到入db的操作处时,我们就启用该线程来“异步”地执行入db的操作。这样,在高并发的情况下,“下一次”的请求就可以不用等到“这一次”入db的操作执行完毕后再执行了。
具体到实际情景就是:如果执行某项任务需要甲乙协作完成,甲做完“下一步”给乙做,而乙做的东西又比较耗时,则此次任务必须是甲等待乙完成之后再接收下一次该任务。其中的等待时间就浪费了。如果是异步的话,则甲完成他的部分,可以不必等待乙完成再接收下一次任务。甲每次完成就把他的结果部分丢给乙,而乙是可以不必跟甲在同一流程里执行的。这样就达到了“错开”的效果。也节省了时间。
当然,我们更为合理的做法是用一个线程池去控制多线程,线程池可以负责多个线程的创建、等待、调度和销毁等。因为如果在高并发的情况下,你每到入db处,就new一个线程,当new的线程越来越多时,会造成内存的大量占用。
拿甲乙的例子来说,线程池就是负责创建了很多个“乙”,他们共同协作完成比较耗时的入db的操作。效率上因此更加高效了。线程池此时可以理解成“包工头”,当一个“乙”做完之后,就可以接着做了,而当一个“乙”正在做,则不会把任务分配给他,只有当他做完之后,才会再次分配入db的任务给他。
我们不妨在web项目初始化的时候,就创建一个线程池的类,这个线程池类定义好了一些多线程的配置然后定义一个调度线程的静态方法。我们需要用线程池调用线程时,就调用该静态方法即可。
需要注意的是,“异步”调用受线程影响很大。就拿上述的入db操作的线程来说,可能在高并发的情形下,线程未能及时被线程池回收而不够用,入db的操作就会被down掉或不能及时入db。所以需要我们好好控制线程池的配置。
【2016-1-18相关补充】
观察下面的代码:
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("模拟订阅"); } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) { try { Thread.sleep(3000); System.out.println("每隔3s do something。。。========"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); // new Thread(new Runnable() { // @Override // public void run() { while(true) { try { Thread.sleep(6000); System.out.println("每隔6s do otherthing。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } // } // }).start(); }
运行该main程序之后,你会发现,虽然第二步用的是一个while循环,但是它是写在一个内部的匿名线程类里面,你会发现还是会重复 打印 [每隔3s do something。。。========]2次之后再打印一次 [每隔6s do otherthing。。。]的情况;程序并没有在第二步被while循环阻塞。
但是如果你第二步不是写在匿名线程里,则编译都会报错,因为第三步根本就变成unreachable的。
注意,上述代码的写法中,第三步的whille循环可用线程套起来也可以不用,但注意一定要sleep , 否则程序会永远阻塞在第三步。第二步就不会每隔一段时间去执行啦。(其实第二步如果你不sleep第三步也是执行不到的,所以无论哪个while都应该sleep)
上面的写法其实之前我也不是很熟悉,但今天碰到了顺便学习了一下。这是非常实用的。比如一个程序启动往往有每隔段时间去检查队列的需求,这时候其实你并不需要劳师动众去专门写一个定时任务(因为此时逻辑很简单哒)。巧妙应用while是完全能达到效果滴。
综上所述,线程其实也是异步的一个很重要的反映。
上一篇: CSS Button
下一篇: 工厂模式 Java设计模式笔记