java并发编程实战-第11章-性能与可伸缩性
首先要保证程序的正确运行,仅当程序的性能需求和测试结果要求程序执行更快的时候,才应该设法提高程
序的运行速度
11.1 对性能的思考
要想通过并发提高性能,需要做好2件事:
更有效的利用现有的处理资源,以及在出现新的处理资源时使程序尽可能的利用新资源
如果是计算密集型的,则可以添加cpu来处理
11.1.1 性能与可伸缩性
本章重点介绍可伸缩性而不是单线程的性能
11.1.2 评估各种性能权衡因素
避免不成熟的优化。首先使城迅速正确,然后再提高运行速度
已测试为基准,不要猜测
11.2 Amdahl 定律
串行总是存在的,该定律告诉我们,为了提高程序的性能,加速比。需要降低程序中串行部分的比例
11.2.1 示例:在各种框架中隐藏的串行部分
比较ConcurrentLinkedQueue 与synchronized Linked List
随着线程数的增加,他们的吞吐率也会提高,但最后都会到达极限,不在增加
11.2.2 Amdahl 定律的应用
如果能准确估计出执行中串行部分所占在比分,则能量化当有多少资源时的加速比
在11.4.2 和11.4.3 节中,介绍的两种降低锁粒度的方法:锁分解和锁分段 。用Amdahl 定律分析,锁分
段更有前途,应为锁分段的数量可以随着处理器数量的增加而增加,串行的比例也会越来越低
11.3 线程引入的开销
11.3.1 上下文切换
当线程由于锁而被阻塞时,jvm通常会把这个线程挂起,并允许它被交换出去。引起上下文切换,所以
仅从性能考虑,阻塞的地方越少越好,无阻塞算法(15章)有助于上下文切换
11.3.2 内存同步
区分有竞争的同步和无竞争的同步非常重要。
syncronized 针对无竞争的同步进行了优化。(volatile通常是非竞争的)
不要过度担心非竞争同步带来的开销。应重点放在锁竞争的地方。
无竞争的同步指加了同步修饰符(syncronized),但实际上该代码是无需同步的 。比如,方法内部对
变量的同步
比如如下代码 Listing 11.3 :jvm会对同步进行锁消除优化:
Listing 11.3. Candidate for Lock Elision.
public String getStoogeNames() {
List<String> stooges = new Vector<String>();
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
return stooges.toString();
}
非竞争的同步可以完全在jvm中处理,而竞争的同步可能需要操作系统的介入,从而增加开销
所以:重点关注:有竞争的同步
11.3.3 阻塞
11.3.1 解释 上下文切换带来开销
11.4 减少锁的竞争
串行操作降低可伸缩性,上下文切换降低性能。在锁上的竞争会同时发生如上两件事情,因此减少锁的
竞争能够提高性能和伸缩性
在并发程序中,对可伸缩性最主要的威胁就是独占方式的资源锁
有3种方式:
1、减少锁的持有时间
2、降低锁的请求频率
3、使用带有协调机制的锁,这些机制允许更高的并发性
11.4.1 缩小锁的范围(“快进快出”)
比如开发方法调用,改为同步方法内部的代码块
11.4.2 减少锁的粒度
这可通过锁分解和锁分段技术来实现
如果一个锁同时保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁 。参考11.6到11.7
的转变
Listing 11.6. Candidate for Lock Splitting.
@ThreadSafe
public class ServerStatus {
@GuardedBy("this") public final Set<String> users;
@GuardedBy("this") public final Set<String> queries;
...
public synchronized void addUser(String u) { users.add(u); }
public synchronized void addQuery(String q) { queries.add(q); }
public synchronized void removeUser(String u) {
users.remove(u);
}
public synchronized void removeQuery(String q) {
queries.remove(q);
}
}
Listing 11.7. ServerStatus Refactored to Use Split Locks.
@ThreadSafe
public class ServerStatus {
@GuardedBy("users") public final Set<String> users;
@GuardedBy("queries") public final Set<String> queries;
...
public void addUser(String u) {
synchronized (users) { // 使用变量的锁,而不是ServerStatus的
锁
users.add(u);
}
}
public void addQuery(String q) {
synchronized (queries) {//使用变量的锁,而不是ServerStatus的
锁
queries.add(q);
}
}
// remove methods similarly refactored to use split locks
}
11.4.3 锁分段
ConcurrentHashMap 参靠list11-8
Listing 11.8. Hash-based Map Using Lock Striping.
@ThreadSafe
public class StripedMap {
// Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS]
private static final int N_LOCKS = 16;
private final Node[] buckets;
private final Object[] locks;
private static class Node { ... }
public StripedMap(int numBuckets) {
buckets = new Node[numBuckets];
locks = new Object[N_LOCKS];
for (int i = 0; i < N_LOCKS; i++)
locks[i] = new Object();
}
private final int hash(Object key) {
return Math.abs(key.hashCode() % buckets.length);
}
public Object get(Object key) {
int hash = hash(key);
synchronized (locks[hash % N_LOCKS]) {
for (Node m = buckets[hash]; m != null; m = m.next)
if (m.key.equals(key))
return m.value;
}
return null;
}
public void clear() {
for (int i = 0; i < buckets.length; i++) {
synchronized (locks[i % N_LOCKS]) {
buckets[i] = null;
}
}
}
...
}
11.4.4 避免热点域
一个看似性能优化的措施:缓存size操作的结果,会变成一个可伸缩性的问题。未避免这个问题,
ConcurrentHashMap为每个分段都维护了一个计数器,而不是一个全局的计数器。
11.4.5 一些替代独占锁的方法
放弃独占锁,例如使用并发容器、读写锁、不可变对象以及原子变量
11. 4.6 监测cpu的利用率
如果cpu未充分利用,通常有以下原因:
负载不充足
I/O 密集
外部限制
锁竞争
如果cpu充分利用、并且总有可用的线程在等待cpu,那么当增加更多处理器的时候,程序性能可能会提提
升
11.4.7 向对象池说不
又是一个看似提高性能的措施,实际上会导致可伸缩性问题
通常,对象的分配操作的开销比同步的开销要低
11.5 比较Map的性能
并发容器与同步容器
单线程情况下,性能相当。2个线程,同步容器性能就变得糟糕了,而并发容器性能提高。线程继续增
加,竞争变得激烈,每个操作的时间大部分用于上下文切换和调度延迟,吞吐率不再提高
11.6 减少上下文切换的开销
以日志操作为例子,通过将io操作从处理请求分离出来,可以缩短服务的请求时间。调用log方法的线
程将不会因为等待输入输出流的锁或io完成而被阻塞。
类似锁分解的方法,请求服务的锁和日志记录的锁从一个锁中分离出来。
小结:
并发性能的重点通常是吞吐率和可伸缩性,而不是服务时间。Amdahl定律告诉告诉我们,程序的可伸缩性
取决于所有代码中串行执行的代码的比例。而java中串行操作的主要来源是独占方式的资源锁,因此可以通
过以下方式来提升性能
:减少锁的持有时间、降低锁的粒度、采用非独占锁或非阻塞锁来代替独占锁
相关推荐
62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java并发编程实战62-Java...
《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...
Java并发编程实践-电子书-01章.pdf Java并发编程实践-电子书-02章.pdf Java并发编程实践-电子书-03章.pdf Java并发编程实践-电子书-04章.pdf Java并发编程实践-电子书-05章.pdf Java并发编程实践-电子书-06章.pdf ...
在《Java并发编程实战》中,读者也能够了解到如何将并发与现代Java语言特性结合起来,例如使用Lambda表达式和Stream API来简化并发代码的编写。 综合来看,该书不仅适合于对Java并发编程感兴趣的初学者,同样也适合...
《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南。这本书旨在帮助开发者理解和掌握在Java环境中创建高效、可扩展且可靠的多线程应用程序的关键技术和实践。它涵盖了从基本概念到高级主题的广泛内容,...
《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南,由Tim Peierls等人与Brian Goetz合著,旨在帮助Java开发者理解和掌握在多线程环境中编写高效、安全的代码。这本书由拥有丰富经验的JDK并发大师及...
《Java并发编程实战》这本书是Java开发者深入理解并发编程的重要参考书籍。本书旨在帮助程序员解决在多线程环境中遇到的实际问题,提升系统性能并保证其稳定性。随书源码提供了丰富的示例,让读者能够动手实践,加深...
在Java并发编程中,数据的封装与访问控制、线程安全性的考量、同步机制的使用是重要的基础概念和技巧。以下是从给出的文件内容中提取出的详细知识点: 1. 数据封装与访问控制:确保内部私有数据不被轻易访问,并且...
《Java 并发编程实战》是一本专注于Java并发编程的权威指南,对于任何希望深入了解Java多线程和并发控制机制的开发者来说,都是不可或缺的参考资料。这本书深入浅出地介绍了如何在Java环境中有效地管理和控制并发...
9. **第九章:实战与性能调优** - 最后一章可能包括了实际并发编程中的最佳实践,性能测试和监控,以及如何根据系统需求进行性能调优。 以上只是基于常规并发编程书籍结构的猜测,具体内容还需要参考实际的电子书...
深入地探讨Java并发编程实践中的显示锁概念,我们聚焦于《Java并发编程实践-电子书-07章》所提供的丰富内容。本章着重于展示如何使用显示锁(也称为高级锁),并详细介绍了Lock和ReentrantLock接口以及它们在并发...
《JAVA并发编程实践》是Java开发人员深入理解并发编程的一本经典著作,由Doug Lea撰写,本书中文版高清完整,包含丰富的书签,便于读者查阅和学习。这本书旨在帮助开发者掌握在Java平台上进行高效、安全并发编程的...
第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注
Java并发编程实践-电子书-09章.pdf
Java并发编程---synchronized关键
Java并发编程实践是Java开发中不可或缺的一个领域,它涉及到如何高效、正确地处理多线程环境中的任务。这本书的读书笔记涵盖了多个关键知识点,旨在帮助读者深入理解Java并发编程的核心概念。 1. **线程和进程的...