`
Everyday都不同
  • 浏览: 713448 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

多线程 & 异步调用 的理解

阅读更多

最近做项目,高并发的情况比较常见,因此常常需要用到多线程。而之前一直对多线程处于一个比较模糊的状态,这次终于清晰了点儿。其实理解多线程可以和异步调用结合起来理解会比较好。

 

对于同步调用和异步调用,可以用以下伪代码来粗略的看一下:

 

同步调用:

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是完全能达到效果滴。

综上所述,线程其实也是异步的一个很重要的反映。

1
4
分享到:
评论
7 楼 haoran_10 2015-09-21  
楼主,异步调用,直接用消息中间件,千万别用多线程处理,性能很差的,用多线程反而会拖累服务器的性能。
6 楼 Everyday都不同 2015-09-18  
18735384527 写道
虽然是多线程,但是cup时刻都只能运行1个线程,如果入库的操作没有线程睡眠之类的操作,那让它入库之后,再重新跑,性能也没什么提升吧

cpu是通过时间分片来实现多线程效果的
5 楼 18735384527 2015-09-17  
虽然是多线程,但是cup时刻都只能运行1个线程,如果入库的操作没有线程睡眠之类的操作,那让它入库之后,再重新跑,性能也没什么提升吧
4 楼 Everyday都不同 2015-09-17  
diarbao 写道
明白你说的意思。
可是现在有一个问题
你怎么能确定你的保存是否成功了呢?
一般情况下我感觉系统都是需要同步进行的呢。
比如你保存是否成功都需要一个返回值 然后来告诉用户 你的操作是否成功了吧?
这个问题是怎么解决的呢。谢谢

您好 ,我这边考虑的是在高并发的情形下,并不是普通的增删改查的“增”。我关心的是在高并发条件下,每次都得入库时,此时不能采取同步的方式。因为高并发,每次都得入库。所以才必须使用异步的策略。而并不关心响应信息。一般这样就会有我说的入db并不及时或出现根本没入库的情况。
3 楼 Everyday都不同 2015-09-17  
cywhoyi 写道
一般不是这么搞,宁愿加lock,也不会采取new thread,哪怕你池子中获取都行,也不要new Thread。
1.耗资源,不管开辟thread,还是回收thread
2.假设很多请求,每次都是new,太可怕不可想象

是的,一般不会去new Thread, 而是用线程池去调用多线程
2 楼 cywhoyi 2015-09-17  
一般不是这么搞,宁愿加lock,也不会采取new thread,哪怕你池子中获取都行,也不要new Thread。
1.耗资源,不管开辟thread,还是回收thread
2.假设很多请求,每次都是new,太可怕不可想象
1 楼 diarbao 2015-09-17  
明白你说的意思。
可是现在有一个问题
你怎么能确定你的保存是否成功了呢?
一般情况下我感觉系统都是需要同步进行的呢。
比如你保存是否成功都需要一个返回值 然后来告诉用户 你的操作是否成功了吧?
这个问题是怎么解决的呢。谢谢

相关推荐

Global site tag (gtag.js) - Google Analytics