一、并发编程问题与解决
- 上下文切换
- 死锁
- 资源限制
1.1 上下文切换
1.1.1 问题
- CPU通过时间片分配算法来循环执行任务,当前任务一个时间片执行完后会切换到下一个 任务,要保存上一个任务的状态,有一定的开销
- 多线程不一定快 - 因为上下文切换的开销
1.1.2 解决
- 无锁并发编程:根据id将数据hash分配到不同线程来避免使用锁
- CAS算法:自旋锁(注意:自旋CPU消耗大)
- 最少线程:避免创建不需要的线程,线程越多,上下文就多,等待线程也多,切换也频繁
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
1.1.3 解决实战
-
工具
-
使用Lmbench3[1]可以测量上下文切换的时长。
-
使用vmstat可以测量上下文切换的次数。
-
下面是利用vmstat测量上下文切换次数的示例。
$ vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 127876 398928 2297092 0 0 0 4 2 2 0 0 99 0 0 0 0 0 127868 398928 2297092 0 0 0 0 595 1171 0 1 99 0 0 0 0 0 127868 398928 2297092 0 0 0 0 590 1180 1 0 100 0 0 0 0 0 127868 398928 2297092 0 0 0 0 567 1135 0 1 99 0 0
CS(Content Switch)表示上下文切换的次数,从上面的测试结果中我们可以看到,上下文每1秒切换1000多次。
-
-
1.jstack命令dump线程信息
jstack 35031 > dump31
-
2.统计线程状态
grep java.lang.Thread.State dump31 | awk '{print $2$3$4$5}' | sort | uniq -c 39 RUNNABLE 21 TIMED_WAITING(onobjectmonitor) 6 TIMED_WAITING(parking) 51 TIMED_WAITING(sleeping) 305 WAITING(onobjectmonitor) 3 WAITING(parking)
-
3.打开dump文件查看WAITING(onobjectmonitor)发现是jboss线程,接收任务太少,大量线程闲着waitting
-
4.配置jboss使用较少线程
-
5.重新统计发现waitting少了,waitting线程少了,上下文切换就少,因为每一次从 WAITTING到RUNNABLE都会进行一次上下文的切换。可以使用vmstat命令验证一下
1.2 死锁
1.2.1 问题
- 线程t1和线程t2互相等待对方释放锁。
1.2.2 解决
-
dump线程查看出现问题的线程 java.lang.Thread.State: BLOCKED
-
避免一个线程同时获取多个锁。
-
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
-
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
-
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
1.3 资源限制
1.3.1 问题
- 硬件资源限制:网络带宽、硬盘读写、CPU处理速度
- 软件资源限制:连接数(数据库,socket)
- 受限于资源,多线程程序依然在串行执行,因为增加了切换上下文和资源调度的开销,反而效率更低
1.3.1 解决
- 硬件资源:多机部署解决
- 软件资源:连接池、io多路复用
- 根据资源限制调整并发配置。