chapter11_性能与可伸缩性_3_线程引入的开销

  • 对于为了提升性能而引入的线程来说, 并行带来的性能提升必须超过并发导致的开销

  • 上下文切换

    (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的实现方式是无脑挂起, 因此需要操作系统的介入开销比较大(而且线程调度还有上下文切换的开销)

猜你喜欢

转载自blog.csdn.net/captxb/article/details/88621212
今日推荐