并发优化
代码能跑不等于代码好。
并发程序尤其如此——100 个线程未必比 10 个线程快。优化方向错了,越努力越糟糕。
优化的四个方向
| 方向 | 方法 | 效果 |
|---|---|---|
| 减少锁竞争 | 减小锁粒度、无锁算法 | 高 |
| 减少阻塞 | 异步、非阻塞 | 中 |
| 减少上下文切换 | 合理线程数 | 中 |
| 提高缓存命中率 | 数据局部性 | 低 |
减少锁竞争
锁是最常见的性能瓶颈。
错误:粗粒度锁
java
public class BadLockExample {
private final Object lock = new Object();
private int a = 0, b = 0, c = 0;
// ❌ 一个锁锁所有操作,线程全排队
public void updateA() {
synchronized (lock) { a++; }
}
public void updateB() {
synchronized (lock) { b++; }
}
public void updateC() {
synchronized (lock) { c++; }
}
}正确:细粒度锁 / 无锁
java
public class FineGrainedLockExample {
// ✅ 每个变量独立锁
private final AtomicInteger a = new AtomicInteger();
private final AtomicInteger b = new AtomicInteger();
private final AtomicInteger c = new AtomicInteger();
// 无锁操作
public void updateA() { a.incrementAndGet(); }
public void updateB() { b.incrementAndGet(); }
public void updateC() { c.incrementAndGet(); }
}正确:分段锁
java
public class SegmentLockExample {
// 分段数组,每段独立锁
private static final int SEGMENTS = 16;
private final Node[] segments;
public SegmentLockExample() {
segments = new Node[SEGMENTS];
}
private Node segmentFor(Object key) {
int hash = key.hashCode() & (SEGMENTS - 1);
synchronized (segments[hash]) {
// ...
}
}
}减少阻塞
阻塞意味着线程空转。
异步 vs 同步
java
public class AsyncVsSync {
// ❌ 同步:线程阻塞等待 I/O
public String syncGet(String url) {
try {
return new URL(url).openStream() // 阻塞
.readAllBytes().toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ✅ 异步:不阻塞线程
public CompletableFuture<String> asyncGet(String url) {
return CompletableFuture.supplyAsync(() -> {
try {
return new URL(url).openStream()
.readAllBytes().toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
public static void main(String[] args) {
AsyncVsSync demo = new AsyncVsSync();
// 同步调用:10 个 URL = 10 个线程 × 100ms = 1000ms
// 异步调用:1 个线程处理 10 个 = 100ms+
}
}非阻塞队列
java
public class NonBlockingExample {
// ❌ 阻塞队列:take() 会阻塞线程
private static final BlockingQueue<Task> blockingQueue =
new LinkedBlockingQueue<>();
// ✅ 非阻塞队列:poll() 立即返回
private static final ConcurrentLinkedQueue<Task> nonBlockingQueue =
new ConcurrentLinkedQueue<>();
public static void main(String[] args) {
// 生产者
for (int i = 0; i < 100; i++) {
nonBlockingQueue.offer(new Task(i));
}
// 消费者(非阻塞轮询)
Task task;
while ((task = nonBlockingQueue.poll()) != null) {
process(task);
}
}
private static void process(Task task) {}
}减少上下文切换
线程数过多会导致大量上下文切换。
合理线程数
java
public class OptimalThreadCount {
public static void main(String[] args) {
int cores = Runtime.getRuntime().availableProcessors();
System.out.println("CPU 核心数: " + cores);
// CPU 密集型:线程数 = 核心数
System.out.println("CPU 密集型线程数: " + cores);
// I/O 密集型:线程数 = 核心数 × (1 + 等待时间/计算时间)
// 假设等待时间是计算时间的 3 倍
System.out.println("I/O 密集型线程数: " + cores * 4);
}
}减少 synchronized 范围
java
public class ReduceSyncScope {
private final Object lock = new Object();
private int sharedValue;
// ❌ 同步范围过大
public void badMethod() {
synchronized (lock) {
// 同步整个方法
calculate();
validate();
sharedValue = result;
}
}
// ✅ 同步范围最小化
public void goodMethod() {
int temp;
// 不需要同步的计算
temp = calculate();
temp = validate(temp);
// 只同步必要的写入
synchronized (lock) {
sharedValue = temp;
}
}
}LongAdder:减少 CAS 竞争
高并发计数器,AtomicLong 有瓶颈。
java
public class LongAdderOptimization {
// ❌ AtomicLong:所有线程竞争同一个 value
private static final AtomicLong counter1 = new AtomicLong();
// ✅ LongAdder:分段累加,减少竞争
private static final LongAdder counter2 = new LongAdder();
public static void main(String[] args) throws InterruptedException {
int threads = 100;
int iterations = 10000;
ExecutorService executor = Executors.newFixedThreadPool(threads);
// AtomicLong 性能测试
long start = System.nanoTime();
for (int i = 0; i < threads; i++) {
executor.submit(() -> {
for (int j = 0; j < iterations; j++) {
counter1.incrementAndGet();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("AtomicLong: " + counter1.get() +
", 耗时: " + (System.nanoTime() - start) / 1_000_000 + "ms");
}
}读写分离
读多写少场景,优化读性能。
java
public class ReadWriteSeparation {
// ❌ 普通 ConcurrentHashMap:读写都要竞争
private static final ConcurrentHashMap<String, String> map1 =
new ConcurrentHashMap<>();
// ✅ CopyOnWriteArrayList:读无锁,写时复制
private static final CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();
public static void main(String[] args) {
// 填充数据
for (int i = 0; i < 10000; i++) {
list.add("item-" + i);
}
// 100 个线程同时读取(无锁)
ExecutorService readers = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
readers.submit(() -> {
for (int j = 0; j < 1000; j++) {
list.contains("item-" + j);
}
});
}
readers.shutdown();
}
}批量操作
减少操作次数。
java
public class BatchOperation {
// ❌ 逐个插入:1000 次网络往返
public void badBatchInsert(List<User> users) {
for (User user : users) {
db.insert(user); // 每次都要网络往返
}
}
// ✅ 批量插入:1 次网络往返
public void goodBatchInsert(List<User> users, int batchSize) {
List<User> batch = new ArrayList<>();
for (User user : users) {
batch.add(user);
if (batch.size() >= batchSize) {
db.batchInsert(batch); // 批量插入
batch.clear();
}
}
if (!batch.isEmpty()) {
db.batchInsert(batch);
}
}
}优化检查清单
| 检查项 | 问题 | 优化 |
|---|---|---|
| 锁粒度 | 一个锁锁所有 | 分离锁或无锁 |
| 锁范围 | 同步整个方法 | 只同步必要代码 |
| 阻塞调用 | 同步 I/O | 异步 I/O |
| 线程数 | 固定 100 线程 | 根据 CPU/IO 调整 |
| 计数器 | AtomicLong | LongAdder |
| 读写比 | 读多写少用 CHM | 考虑 CopyOnWrite |
注意事项
- 先测量再优化:用 profiler 找瓶颈,不要猜
- 避免过早优化:可读性优先,不确定时不要优化
- 优先减少锁竞争:收益最高
- 一致性 vs 性能:CAP 权衡,合适就好
要点回顾
- 减少锁竞争:减小锁粒度、无锁算法、分段锁
- 减少阻塞:异步 I/O、非阻塞队列
- 减少上下文切换:合理线程数
- LongAdder:高并发计数器的首选
- 读写分离:读多写少用 CopyOnWrite
- 批量操作:减少网络往返
