上下文切换是什么
- 分时系统中,一个线程被暂停剥夺使用权,另外一个线程被选中开始或者继续运行的过程就是上下文切换。
- 一个线程的状态由 RUNNING 转为 BLOCKED ,再由 BLOCKED 转为 RUNNABLE ,然后再被调度器.选中执行,这就是一个上下文切换的过程。
线程生命周期
线程主要有“新建”(NEW)、“就绪”(RUNNABLE)、“运行”(RUNNING)、“阻塞”(BLOCKED)、“死亡”(DEAD)五种状态。到了 Java 层面它们都被映射为了 NEW、RUNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINADTED 等 6 种状态(见java.lang.Thread.State)。
- java的RUNABLE对应于OS的RUNNABLE,RUNNING,BLOCKED状态。例如IO阻塞在java中也是RUNABLE状态。因为Java的线程状态是服务于监控的
- 只有synchronized会导致java线程阻塞
- Thread.sleep,Object.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil会导致java线程进入等待
上下文切换的诱因
- 程序本身触发的切换
- 由系统或者虚拟机诱发的非自发性上下文切换
切换过程的主要开销
- 操作系统保存和恢复上下文
- 调度器进行线程调度
- 处理器高速缓存重新加载
- 上下文切换也可能导致整个高速缓存区被冲刷,从而带来时间开销
Synchronized和Lock对比
从性能方面上来说,在并发量不高、竞争不激烈的情况下,Synchronized 同步锁由于具有分级锁的优势,性能上与 Lock 锁差不多;但在高负载、高并发的情况下,Synchronized 同步锁由于竞争激烈会升级到重量级锁,性能则没有 Lock 锁稳定。
容器选择
多线程优化
竞争锁优化
- 减少锁的时间
- 降低锁的力度
- 锁分离(读写锁)
- 锁分段(里尔Java1.8 之前版本的 ConcurrentHashMap)
- 非阻塞乐观锁替代竞争锁
优化 wait/notify 的使用,减少上下文切换
合理地设置线程池大小
先根据业务类型选择公式计算后再结合压测进行调整。我们要提高线程池的处理能力,一定要先保证一个合理的线程数量,也就是保证 CPU 处理线程的最大化。在此前提下,我们再增大线程池队列,通过队列将来不及处理的线程缓存起来。在设置缓存队列时,我们要尽量使用一个有界队列,以防因队列过大而导致的内存溢出问题。
- CPU 密集型任务:线程数设置为N+1.CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响
- I/O密集型任务:线程数设置为2N.
使用协程实现非阻塞等待
减少 Java 虚拟机的垃圾回收
参考资料
- https://my.oschina.net/goldenshaw/blog/705397