第18章

⚡ 并发性能调优

掌握并发性能测试工具、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大小

并发标记参数

锁竞争分析

锁竞争是并发应用性能瓶颈的主要原因之一。通过分析锁的使用情况、竞争程度和持有时间,我们可以识别并优化性能热点。

锁监控工具

使用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:伪共享优化
创建一个存在伪共享问题的程序,然后使用缓存行填充技术解决问题,测量性能改进效果。
上一章:并发编程模式 返回目录 下一章:并发编程陷阱