第18章
⚡ 并发性能调优
掌握并发性能测试工具、JVM参数调优、锁竞争分析、无锁编程技巧和CPU缓存优化
学习目标
- 掌握并发性能测试工具的使用方法
- 学会JVM并发参数调优技巧
- 进行锁竞争分析和优化
- 了解无锁编程技巧和实现方式
- 掌握CPU缓存优化和伪共享解决方案
性能测试工具
并发性能调优的第一步是准确测量和分析性能问题。Java生态系统提供了多种强大的性能测试和分析工具,帮助我们识别瓶颈、测量改进效果。
JMH (Java Microbenchmark Harness)
JMH是OpenJDK提供的微基准测试框架,专门用于测量Java代码的性能。它能够避免JVM优化带来的测量误差,提供准确的性能数据。
JMH基准测试示例
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class ConcurrentMapBenchmark {
private ConcurrentHashMap concurrentMap;
private Map synchronizedMap;
@Setup
public void setup() {
concurrentMap = new ConcurrentHashMap<>();
synchronizedMap = Collections.synchronizedMap(new HashMap<>());
// 预填充数据
for (int i = 0; i < 1000; i++) {
String key = "key" + i;
String value = "value" + i;
concurrentMap.put(key, value);
synchronizedMap.put(key, value);
}
}
@Benchmark
@Threads(4)
public String testConcurrentHashMap() {
return concurrentMap.get("key" + ThreadLocalRandom.current().nextInt(1000));
}
@Benchmark
@Threads(4)
public String testSynchronizedMap() {
return synchronizedMap.get("key" + ThreadLocalRandom.current().nextInt(1000));
}
}
JProfiler
商业性能分析工具,提供CPU分析、内存分析、线程分析等功能,界面友好,功能强大。
VisualVM
免费的性能分析工具,集成在JDK中,支持CPU和内存分析、线程监控、MBean管理等。
Arthas
阿里开源的Java诊断工具,支持在线问题诊断、性能监控、类加载信息查看等。
JVM并发参数调优
JVM提供了众多参数来优化并发性能,包括垃圾收集器选择、线程栈大小、偏向锁设置等。合理配置这些参数能够显著提升并发应用的性能。
垃圾收集器参数
GC参数优化
- -XX:+UseG1GC:使用G1垃圾收集器,适合大堆内存和低延迟要求
- -XX:MaxGCPauseMillis=200:设置GC最大暂停时间目标
- -XX:G1HeapRegionSize=16m:设置G1区域大小
- -XX:+UseStringDeduplication:启用字符串去重功能
线程相关参数
JVM线程参数配置
# 线程栈大小设置
-Xss1m
# 偏向锁相关参数
-XX:+UseBiasedLocking # 启用偏向锁(JDK 8默认开启)
-XX:BiasedLockingStartupDelay=0 # 立即启用偏向锁
# 自适应自旋锁
-XX:+UseSpinning # 启用自旋锁
-XX:PreBlockSpin=10 # 自旋次数
# 线程本地分配缓冲区
-XX:+UseTLAB # 启用TLAB
-XX:TLABSize=1m # TLAB大小
并发标记参数
- -XX:ConcGCThreads=4:设置并发GC线程数
- -XX:ParallelGCThreads=8:设置并行GC线程数
- -XX:+UseConcMarkSweepGC:使用CMS收集器
- -XX:+CMSParallelRemarkEnabled:启用并行重标记
锁竞争分析
锁竞争是并发应用性能瓶颈的主要原因之一。通过分析锁的使用情况、竞争程度和持有时间,我们可以识别并优化性能热点。
锁监控工具
使用JStack分析锁竞争
# 生成线程转储
jstack > thread_dump.txt
# 查找锁竞争信息
grep -A 10 -B 5 "waiting for monitor entry" thread_dump.txt
grep -A 10 -B 5 "waiting on condition" thread_dump.txt
锁竞争检测代码
public class LockContentionDetector {
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
public void detectLockContention() {
// 启用线程竞争监控
if (threadBean.isThreadContentionMonitoringSupported()) {
threadBean.setThreadContentionMonitoringEnabled(true);
}
// 定期检查线程状态
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
ThreadInfo[] threadInfos = threadBean.getAllThreadInfo();
for (ThreadInfo info : threadInfos) {
if (info.getBlockedTime() > 1000) { // 阻塞超过1秒
System.out.printf("Thread %s blocked for %d ms%n",
info.getThreadName(), info.getBlockedTime());
}
if (info.getWaitedTime() > 5000) { // 等待超过5秒
System.out.printf("Thread %s waited for %d ms%n",
info.getThreadName(), info.getWaitedTime());
}
}
}, 0, 5, TimeUnit.SECONDS);
}
}
锁优化策略
减少锁粒度
将大锁拆分为多个小锁,减少锁竞争的范围和时间。
减少锁持有时间
优化临界区代码,尽快释放锁资源。
锁分离
读写分离,使用ReadWriteLock等专用锁。
无锁编程技巧
无锁编程通过CAS(Compare-And-Swap)操作和原子类来避免传统锁的开销,在高并发场景下能够提供更好的性能。
CAS操作原理
CAS核心思想
CAS操作包含三个参数:内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,CAS才会将V的值设为B,否则不做任何操作。
无锁计数器实现
public class LockFreeCounter {
private final AtomicLong count = new AtomicLong(0);
public long increment() {
return count.incrementAndGet();
}
public long decrement() {
return count.decrementAndGet();
}
public long get() {
return count.get();
}
// 自定义CAS操作
public boolean compareAndSet(long expect, long update) {
return count.compareAndSet(expect, update);
}
// 无锁累加操作
public long addAndGet(long delta) {
long current;
long next;
do {
current = count.get();
next = current + delta;
} while (!count.compareAndSet(current, next));
return next;
}
}
无锁数据结构
无锁队列实现
public class LockFreeQueue {
private static class Node {
volatile T data;
volatile Node next;
Node(T data) {
this.data = data;
}
}
private final AtomicReference> head;
private final AtomicReference> tail;
public LockFreeQueue() {
Node dummy = new Node<>(null);
head = new AtomicReference<>(dummy);
tail = new AtomicReference<>(dummy);
}
public void enqueue(T item) {
Node newNode = new Node<>(item);
while (true) {
Node last = tail.get();
Node next = last.next;
if (last == tail.get()) { // 确保一致性
if (next == null) {
// 尝试链接新节点
if (last.compareAndSetNext(null, newNode)) {
break; // 成功退出
}
} else {
// 帮助推进tail
tail.compareAndSet(last, next);
}
}
}
// 推进tail
tail.compareAndSet(tail.get(), newNode);
}
public T dequeue() {
while (true) {
Node first = head.get();
Node last = tail.get();
Node next = first.next;
if (first == head.get()) { // 确保一致性
if (first == last) {
if (next == null) {
return null; // 队列为空
}
// 帮助推进tail
tail.compareAndSet(last, next);
} else {
T data = next.data;
// 尝试推进head
if (head.compareAndSet(first, next)) {
return data;
}
}
}
}
}
}
CPU缓存优化
CPU缓存的使用效率直接影响并发程序的性能。理解缓存行、伪共享等概念,并采用相应的优化技术,能够显著提升程序性能。
伪共享问题
伪共享危害
当多个线程修改同一缓存行中的不同变量时,会导致缓存行在CPU核心间频繁传输,严重影响性能。
伪共享问题示例
// 存在伪共享问题的代码
public class FalseSharingExample {
private volatile long value1;
private volatile long value2;
private volatile long value3;
private volatile long value4;
// 多个线程同时修改这些变量会导致伪共享
}
解决伪共享
缓存行填充解决方案
// 使用@Contended注解(JDK 8+)
@sun.misc.Contended
public class ContendedExample {
private volatile long value;
}
// 手动填充缓存行
public class PaddedAtomicLong {
// 前填充
private volatile long p1, p2, p3, p4, p5, p6, p7;
// 实际数据
private volatile long value;
// 后填充
private volatile long p8, p9, p10, p11, p12, p13, p14;
public long get() {
return value;
}
public void set(long newValue) {
value = newValue;
}
}
// 使用继承方式填充
abstract class PaddedAtomicReference extends AtomicReference {
// 缓存行填充
protected volatile long p1, p2, p3, p4, p5, p6, p7;
protected volatile long p8, p9, p10, p11, p12, p13, p14, p15;
}
内存布局优化
数据局部性
将相关数据放在一起,提高缓存命中率。
缓存行对齐
确保关键数据结构按缓存行边界对齐。
避免伪共享
使用填充或@Contended注解分离热点数据。
实战练习
练习1:JMH性能测试
使用JMH对不同的并发集合进行性能基准测试,比较ConcurrentHashMap、Collections.synchronizedMap和Hashtable的性能差异。
练习2:锁竞争分析
创建一个高并发场景,使用JProfiler或VisualVM分析锁竞争情况,并实施优化方案。
练习3:无锁队列实现
实现一个无锁的环形缓冲区,并与基于锁的实现进行性能对比。
练习4:伪共享优化
创建一个存在伪共享问题的程序,然后使用缓存行填充技术解决问题,测量性能改进效果。