上下文切换
单核处理器是如何支持多线程编程的? CPU通过给每个线程分配CPU时间片来实现。
- 时间片是CPU分配给各个线程的时间,时间片非常短,CPU通过不停切换线程执行,切换时间很快,让人感觉是在同时执行。
- CPU通过时间片算法来循环执行任务,当前任务执行完一个时间片后会切换到下一个任务,切换前辉保存上一个任务的状态,便于下次切回的时候可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
- 因为线程有创建和上下文的切换的开销,并发执行累加操作次数没有很高的时候,速度会比串行执行慢。
测试上下文切换次数和时长的工具
- 使用Lmbench3可以测量上下文切换的时长
- 使用vmstat可以测量上下文切换的次数
如何减少上下文切换
- 无锁并发编程。多线程竞争锁时,会引起上下文切换,可以用些方法来避免用锁。比如对数据id计算取模分段,不同线程处理不同段的数据。
- CAS算法。CAS:Compare and Swap, 翻译成比较并交换。
java.util.concurrent
包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。 - 使用最少线程。避免创建不需要的线程。比如任务很少,不比创建大量线程,可能会导致大量线程处于等待状态。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。具体java如何实现协程要找个时间研究下。
死锁
dump分析死锁
- 一旦出现死锁,业务是可感知的,不能继续提供服务。只能通过dump线程查看是哪个线程出现了问题。
- Jstack是JDK自带的命令行工具,主要用于线程Dump分析
-
我们先用Jps来查看java进程id(或者Linux的ps命令)
image.png -
jstack的使用
image -
jstack输出线程dump信息到文件
image -
查看文件,关键字是at DeadThread.run,比如at DeadThread.run(DeadThread.java:37),说明Thread-1实在DeadThread类的37行处发生死锁
image.png
避免死锁的常见方法
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用
lock.tryLock(timeout)
来替代使用内部锁机制。 - 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失效的情况。