-
对于为了提升性能而引入的线程来说, 并行带来的性能提升必须超过并发导致的开销
-
上下文切换
(1) 如果可运行的线程数量大于CPU的数量, 那么操作系统每隔一段时间会将某个线程调度出来, 这回导致一次上下文切换
(2) 应用程序、JVM、操作系统共享一组CPU, 因此切换消耗的CPU时钟周期长, 意味着留给应用程序的时钟周期短
(3) 调度会为每个可运行的线程分配一个__最小执行时间__
原因是: 将上下文切换的开销分摊到不会中断的执行时间上, 实现整体的吞吐量高, 代价是响应变慢
(4) 当线程由于等待某个发生竞争的锁而阻塞时, JVM会将这个线程挂起
(5) 如果阻塞频繁发生, 意味着上下文切换次数很多, 增加了调度开销(而这一部分涉及到操作系统的内核)
(6) 如果内核占用率较高, 那很可能说明调度活动太频繁, 可能存在IO或者竞争锁导致的阻塞
-
内存同步
(1) synchronized和volatile保证了可见性,引入了__内存栅栏__机制: 刷新缓存使缓存无效
(2) 内存栅栏机制导致了刷新缓存的开销
(3) 同时, synchronized和volatile保证的有序性减少了指令重排序, 抑制了编译器的优化操作
(4) 当评价内存同步的开销时, 应该区分__有竞争同步__和__无竞争同步__。
(5) 无竞争同步对程序的整体影响很小, 因为现代JVM会对无竞争同步做很多优化
1° 进行__逸出分析__, 找出不会被其他线程引用的变量, 去掉上面的锁
2° 进行__锁粒度粗化__, 例如临近的同步代码块可以用同一个锁合并
-
阻塞
(1) 非竞争同步可以在JVM中处理, 而__竞争同步还需要操作系统的介入__, 增加了开销
(2) JVM发现线程阻塞时, 可以有两种处理方式
1° 使用__自旋锁__等待: 不断轮询这个锁直到成功
2° 通过操作系统__挂起__被阻塞的线程
(3) 如果等待时间较短, 应该使用自旋锁; 如果等待时间较长, 应该使用线程挂起
但是, 大部分JVM的实现方式是无脑挂起, 因此需要操作系统的介入开销比较大(而且线程调度还有上下文切换的开销)
chapter11_性能与可伸缩性_3_线程引入的开销
最新推荐文章于 2023-08-27 19:52:38 发布