变形计嘉宾,男子好赌被妻子乱棍打死,海阳人才招聘
本文来自方腾飞老师《java并发编程的艺术》第一章。
并发编程的目的是为了让程序运行得更快,但是并不是启动更多的线程就能让程序最大限度地并发执行。在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制问题,本文要研究的是上下文切换的问题。
cpu通过时间片段的算法来循环执行线程任务,而循环执行即每个线程允许运行的时间后的切换,而这种循环的切换使各个程序从表面上看是同时进行的。而切换时会保存之前的线程任务状态,当切换到该线程任务的时候,会重新加载该线程的任务状态。从任务保存到再加载的过程就是一次上下文切换。
我们可以通过对比串联执行和并发执行进行对比。
1 private static final long count = 1000000; 2 3 public static void main(string[] args) throws exception { 4 concurrency(); 5 series(); 6 } 7 /** 8 * 并发执行 9 * @throws exception 10 */ 11 private static void concurrency() throws exception { 12 long start = system.currenttimemillis(); 13 //创建线程执行a+= 14 thread thread = new thread(new runnable() { 15 public void run() { 16 int a = 0; 17 for (int i = 0; i < count; i++) { 18 a += 1; 19 } 20 } 21 }); 22 //启动线程执行 23 thread.start(); 24 //使用主线程执行b--; 25 int b = 0; 26 for (long i = 0; i < count; i++) { 27 b--; 28 } 29 //合并线程,统计时间 30 thread.join(); 31 long time = system.currenttimemillis() - start; 32 system.out.println("concurrency:" + time + "ms, b = " + b); 33 } 34 /** 35 * 串联执行 36 */ 37 private static void series() { 38 long start = system.currenttimemillis(); 39 int a = 0; 40 for (long i = 0; i < count; i++) { 41 a += 1; 42 } 43 int b = 0; 44 for (int i = 0; i < count; i++) { 45 b--; 46 } 47 long time = system.currenttimemillis() - start; 48 system.out.println("serial:" + time + "ms, b = " + b + ", a = " + a); 49 }
修改上面的count值,即修改循环次数,对比一下串行运行和并发运行的时间测试结果:
循环次数 | 串行执行耗时/ms | 并发执行耗时/ms | 串行和并发对比 |
1亿 | 78 | 50 | 并发快约0.5倍 |
1000万 | 10 | 6 | 并发快约0.5~1倍 |
100万 | 3 | 2 | 差不多 |
10万 | 2 | 2 | 差不多 |
1万 | 0 | 1 | 差不多,十几次执行下来,总体而言串行略快 |
从表中可以看出,100次并发执行累加以下,串行执行和并发执行的运行速度总体而言差不多,1万次以下串行执行甚至还
在linux系统下可以使用vmstat命令来查看上下文切换的次数,如果要查看上下文切换的时长,可以利用lmbench3,这是一个性能分析工具。通过数据的对比我们可以看出。在一万以下的循环次数时,串联的执行速度比并发的执行速度块。是因为线程上下文切换导致额外的开销。
四、引起线程上下文切换的原因
对于我们经常使用的抢占式操作系统而言,引起线程上下文切换的原因大概有以下几种:
五、上下文切换次数查看
在linux系统下可以使用vmstat命令来查看上下文切换的次数,下面是利用vmstat查看上下文切换次数的示例:
cs(context switch)表示上下文切换的次数,从图中可以看到,上下文每秒钟切换500~600次左右。
如果要查看上下文切换的时长,可以利用lmbench3,这是一个性能分析工具。
减少上下文切换次数便可以提高多线程的运行效率。减少上下文切换的方法有无锁并发编程、cas算法、避免创建过多的线程和使用协程。
无锁并发编程. 当任何特定的运算被阻塞的时候,所有cpu可以继续处理其他的运算。换种方式说,在无锁系统中,当给定线程被其他线程阻塞的时候,所有cpu可以不停的继续处理其他工作。无锁算法大大增加系统整体的吞吐量,因为它只偶尔会增加一定的交易延迟。大部分高端数据库系统是基于无锁算法而构造的,以满足不同级别。
cas算法。java提供了一套原子性操作的数据类型(java.util.concurrent.atomic包下),使用cas算法来更新数据,不需要加锁。如:atomicinteger、atomiclong等。
避免创建过多的线程。如任务量少时,尽可能减少创建线程。对于某个时间段任务量很大的这种情况,我们可以通过线程池来管理线程的数量,避免创建过多线程。
协程:即协作式程序,其思想是,一系列互相依赖的协程间依次使用cpu,每次只有一个协程工作,而其他协程处于休眠状态。如:java中使用wait和notify来达到线程之间的协同工作。
参考:
《java并发编程的艺术》
作者:calvin_di
链接:https://www.jianshu.com/p/19fc8aca712c
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
apollo与springboot集成实现动态刷新配置的教程详解
网友评论