以下思维导图根据《并发编程的艺术》一书总结而来,该系列文章是对读书笔记的总结及感悟。本文围绕以下几个模块展开,以直白的语言阐述自己的理解,不当之处还望指出。
1.思维导图
2.原理阐述
3.对实际开发的帮助(混合在原理阐述中)
===============================华丽丽的分割线===========================
1.思维导图
2.原理阐述
并发编程目的就是让程序运行的更快,最起码要比串行的快。但是会带来很多问题,如上下文切换,因资源竞争而可能产生的死锁问题,软硬件资源限制问题等等。需要注意的是,尽管对同一份资源进行竞争,也不一定会有数据安全问题。因为如果竞争的是同一份资源不同的数据段其实是不存在这个问题的,如并发包中的ConcurrentHashMap的设计就是这样一种思想。后续会再讨论这个问题。下面就以刨根问底的态度来扒一扒并发编程所要面临的这些问题,或许从这些问题中我们能得到一些启发和帮助,先看第一问题:
一.上下文切换
1.什么是上下文切换,为什么频繁切换可能会导致多线程运行效率低?
2.什么情况会引起上下文切换?
3.如何监测上下文切换?
4.如果确定是上下文切换导致的程序运行缓慢该如何处理?
CPU通过分配时间片来让每个线程轮转执行,如果一个线程在时间片用完时,仍未执行完成,那么系统会保存该线程的状态并让出CPU的使用权,此时拥有更高优先级的线程获得执行机会去执行,在时间片用完后如果上一个线程获得执行机会,那么就需要加载之前的执行状态以便于继续执行,这种线程从保存状态到加载的过程就是一次上下文切换。上下文切换时Linux内核层面进行的,如果频繁的切换势必会影响程序执行速度。
那么什么情况下会引起上下文切换呢?我们可以从线程的状态中得到启示,线程的状态保存起来也就是线程处理等待状态,加载的过程也就是可运行状态,即从waitting状态到runnable状态会引发该情况,上下文切换大致分这么几类:
- 当前任务的时间片用完之后,系统CPU正常调度下一个任务;
- 当前任务碰到IO阻塞,调度线程将挂起此任务,继续下一个任务;
- 多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下一个任务;
- 用户代码挂起当前任务,让出CPU时间;
- 硬件中断;
有两个工具可以检测到上下文切换的时长和次数,lmbench用来检测切换时长,lmbench是一个微测评工具,衡量指标有两个:带宽和时长,其功能可网上查看,vmstat可以用来查看切换次数。频繁的上下文切换带来的明显感知就是CPU负载较高,因为切换时需要频繁的和寄存器和工作队列进行交互。
线上问题的排查措施:一般CPU飙高的情况下,我们可以考虑是否是上下文切换引起的,通过vmstat进行确认,还需综合考虑其他的指标就行排查,附上两个不错的例子看看:
https://www.jianshu.com/p/7d651c88ff4a
https://blog.csdn.net/mimi_csdn/article/details/79375471
排查问题的过程会用到这些工具,不必记忆,用到再查也无妨:vmstat、iostat、top、free、pidstat等。
如果确实是上下文切换引起的问题,在代码层面可以做想应的改动,比如任务很少,线程很多的情况下,可以减少线程数,使用无锁编程,CAS等特性。
二.死锁
1.什么是死锁?
2.产生的原因?
3.发生死锁怎么办?
问题的答案从下文中找,先附上一个死锁的例子:
package javaConcurrency;
public class DeadLockDemo {
private static String A = "a";
private static String B = "b";
public static void main(String[] args) {
DeadLockDemo.deadLock();
}
private static void deadLock() {
new Thread(new Runnable() {
@SuppressWarnings("static-access")
@Override
public void run() {
synchronized (A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("i need B");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (B) {
synchronized (A) {
System.out.println("i need A");
}
}
}
}).start();
}
}
死锁就是我要的东西在你手里,你不给我,你要的东西在我手里,我也不给你,大家就这么僵着吧,耗着吧,互相持有对方的资源都不释放这就是死锁。从上面的例子中我们可以看出产生死锁的一个原因是同时竞争多个资源,还有一种情况是程序发生异常锁没有释放也会引发这种情况。
发生这种情况该如何排查和处理呢?首先在应用层面的感知就是应用卡住了,不动了,这个时候就要打出线程的dump文件,看看是不是Blocked的对象是不是一样的。那么代码层面就是避免一个线程竞争多个锁,一个线程竞争一个锁,使用有超时的lock锁,连接资源的释放要及时。
三.资源限制
不做详细阐述,一般分为软件和硬件,硬件不够了加机器并行,有了Mapreduce。软件的话,比如一些连接池资源的复用等。