如何提高 Java 并行程序性能?

2015-11-24 17:04:01 +08:00
 fangbei

在 Java 程序中,多线程几乎已经无处不在。与单线程相比,多线程程序的设计和实现略微困难,但通过多线程,我们却可以获得多核 CPU 带来的性能飞跃,从这个角度说,多线程是一种值得尝试的技术。那么如何写出高效的多线程程序呢?
1 、有关多线程的误区:线程越多,性能越好
不少初学者可能认为,线程数量越多,那么性能应该越好。因为程序给我们的直观感受总是这样。一个两个线程可能跑的很难,线程一多可能就快了。但事实并非如此。因为一个物理 CPU 一次只能执行一个线程,多个线程则意味着必须进行线程的上下文切换,而这个代价是很高的。因此,线程数量必须适量,最好的情况应该是 N 个 CPU 使用 N 个线程,并且让每个 CPU 的占有率都达到 100%,这种情况下,系统的吞吐量才发挥到极致。但现实中,不太可能让单线程独占 CPU 达到 100%,一个普遍的愿意是因为 IO 操作,无论是磁盘 IO 还是网络 IO 都是很慢的。线程在执行中会等待,因此效率就下来了。这也就是为什么在一个物理核上执行多个线程会感觉效率高了,对于程序调度来说,一个线程等待时,也正是其它线程执行的大好机会,因此, CPU 资源得到了充分的利用。

2 、尽可能不要挂起线程
多线程程序免不了要同步,最直接的方法就是使用锁。每次只允许一个线程进入临界区,让其它相关线程等待。等待有 2 种,一种是直接使用操作系统指令挂起线程,另外一种是自旋等待。在操作系统直接挂起,是一种简单粗暴的实现,性能较差,不太适用于高并发的场景,因为随之而来的问题就是大量的线程上下文切换。如果可以,尝试一下进行有限的自旋等待,等待不成功再去挂起线程也不迟。这样很有可能可以避免一些无谓的开销。 JDK 中 ConcurrentHashMap 的实现里就有一些自旋等待的实现。此外 Java 虚拟机层面,对 synchronized 关键字也有自旋等待的优化。

3 、善用“无锁”
阻塞线程会带来性能开销,因此,一种提供性能的方案就是使用无锁的 CAS 操作。 JDK 中的原子类,如 AtomicInteger 正是使用了这种方案。在高并发环境中,冲突较多的情况下,它们的性能远远好于传统的锁操作(《实战 Java 高并发程序设计》 P158 )。

4 、处理好“伪共享”问题
大家知道, CPU 有一个高速缓存 Cache 。在 Cache 中,读写数据的最小单位是缓存行,如果 2 个变量存在一个缓存行中,那么在多线程访问中,可能会相互影响彼此的性能。因此将变量存放于独立的缓存行中,也有助于变量在多线程访问是的性能提升(《实战 Java 高并发程序设计》 P200 ),大量的高并发库都会采用这种技术。

1652 次点击
所在节点    问与答
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/238566

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX