性能与可伸缩性

多个线程总会引入一些额外的性能开销, 造成这些开销的操作包括:

  • 线程之间的协调(例如加锁, 触发信号以及内存同步等)
  • 增加的上下文切换
  • 线程的创建与销毁
  • 线程的调度等

可伸缩性

  • 可伸缩性指的是: 当增加计算资源时(例如CPU, 内存, 存储容量或I/O带宽), 程序的吞吐量或处理能力能相应的增加
  • 在进行可伸缩性调优时, 其目的是设法将问题的计算并行化, 从而能利用更多的计算资源来完成更多的工作

Amdahl定律

image.png

减少锁的竞争

在并发程序中, 对可伸缩性的最主要威胁就是独占方式的资源锁, 有3种方式可以降低锁的竞争程度:

  • 减少锁的持有时间
  • 降低锁的请求频率
  • 使用带有协调机制的独占锁, 这些机制允许更高的并发性

缩小锁的范围

可以缩短锁的持有时间, 例如可以将一些”大量”的计算或阻塞操作从同步代码块中移出.

减小锁的粒度

可以降低线程请求锁的频率, 可以通过锁分段或锁分解来实现
例如ConcurrentHashMap的实现中使用了一个包含了16个锁的数组, 每个锁保护所有散列桶的1/16, 其中第N个散列桶由第(N mod 16)个锁来保护.

锁分段的一个劣势在于, 与采用单个锁来实现独占访问相比, 要获取多个锁来实现独占访问将更加困难并且开销更高, 比如ConcurrentHashMap的rehash操作

避免热点域
一些常见的优化措施, 例如将一些反复计算的结果缓存起来, 都会引入一些热点域, 而这些热点域往往会限制可伸缩性
例如HashMap的size方法, 常见的优化措施是在put和remove方法中更新一个计数器, 但在锁分段技术实现散列链时, 在对计数器的访问进行同步会导致独占锁的可伸缩性问题, ConcurrentHashMap为了避免这个问题, 为每个分段都维护了一个独立的计数, 并通过每个分段的锁来维护这个值, size将对每个分段进行枚举并加和得到结果

放弃使用独占锁

使用例如并发容器, 读-写锁, 不可变对象或原子变量.
例如使用原子变量来降低更新热点域的开销