深入理解Java并发编程:从synchronized到CompletableFuture

引言

在现代软件开发中,并发编程已经成为构建高性能应用的核心技能。Java作为企业级开发的主流语言,提供了丰富而强大的并发工具。本文将带你从最基础的synchronized关键字开始,一路探索到现代异步编程的利器CompletableFuture,帮助你建立完整的Java并发编程知识体系。

一、并发编程的基石:synchronized

1.1 为什么需要synchronized

在多线程环境中,当多个线程同时访问共享资源时,可能会导致数据不一致的问题。这就是经典的"竞态条件"(Race Condition)。

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++;  // 非原子操作,存在线程安全问题
    }
}

上面的代码看似简单,但count++实际上包含三个步骤:读取、加1、写回。在多线程环境下,这三个步骤可能会被其他线程打断,导致计数错误。

1.2 synchronized的使用方式

synchronized关键字提供了三种使用方式:

方法级别同步:

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

代码块同步:

public class Counter {
    private int count = 0;
    private final Object lock = new Object();
    
    public void increment() {
        synchronized(lock) {
            count++;
        }
    }
}

静态方法同步:

public class Counter {
    private static int count = 0;
    
    public static synchronized void increment() {
        count++;
    }
}

1.3 synchronized的原理

synchronized的底层是通过监视器锁(Monitor)实现的。每个Java对象都可以作为锁,当线程进入synchronized代码块时,它会尝试获取对象的监视器锁。如果锁已被其他线程持有,当前线程会进入阻塞状态。

JVM对synchronized进行了多项优化,包括:

  • 偏向锁:在只有一个线程访问时,减少锁开销
  • 轻量级锁:使用CAS操作避免互斥量的使用
  • 锁粗化:合并相邻的同步块
  • 锁消除:JIT编译时移除不必要的锁

二、轻量级同步:volatile关键字

2.1 volatile的作用

volatile关键字解决两个问题:

  1. 可见性:保证变量修改对所有线程立即可见
  2. 有序性:防止指令重排序
public class VolatileExample {
    private volatile boolean flag = false;
    
    public void writer() {
        flag = true;  // 写操作
    }
    
    public void reader() {
        if (flag) {  // 读操作,能立即看到flag的变化
            // 执行某些操作
        }
    }
}

2.2 volatile vs synchronized

volatile不能替代synchronized,因为它不保证原子性:

public class VolatileCounter {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // 仍然不是线程安全的!
    }
}

volatile适用于以下场景:

  • 状态标志
  • 一写多读的场景
  • 双重检查锁定(DCL)模式
public class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

三、显式锁:Lock接口

3.1 Lock接口的优势

Java 5引入了Lock接口,提供了比synchronized更灵活的锁机制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();  // 必须在finally中释放锁
        }
    }
}

Lock接口的优势包括:

  • 可中断的锁获取:支持lockInterruptibly()
  • 尝试非阻塞获取锁:支持tryLock()
  • 超时获取锁:支持tryLock(long time, TimeUnit unit)
  • 公平锁:可以选择公平或非公平策略

3.2 ReentrantLock的高级用法

public class AdvancedLockExample {
    private final ReentrantLock lock = new ReentrantLock(true);  // 公平锁
    
    public void timeoutLock() {
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    // 执行临界区代码
                } finally {
                    lock.unlock();
                }
            } else {
                // 获取锁超时的处理
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

3.3 ReadWriteLock读写锁

对于读多写少的场景,ReadWriteLock可以显著提升性能:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    private final Map<String, Object> cache = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    public Object get(String key) {
        rwLock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    public void put(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

四、线程池:高效的线程管理

4.1 为什么使用线程池

频繁创建和销毁线程会带来性能开销。线程池通过复用线程来解决这个问题:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    public void executeTask() {
        executor.submit(() -> {
            // 执行任务
            System.out.println("任务执行中: " + Thread.currentThread().getName());
        });
    }
    
    public void shutdown() {
        executor.shutdown();
    }
}

4.2 线程池的核心参数

使用ThreadPoolExecutor可以精确控制线程池行为:

import java.util.concurrent.*;

public class CustomThreadPool {
    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
        5,                      // 核心线程数
        10,                     // 最大线程数
        60L,                    // 空闲线程存活时间
        TimeUnit.SECONDS,       // 时间单位
        new LinkedBlockingQueue<>(100),  // 工作队列
        new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
    );
}

线程池的工作流程:

  1. 当前线程数 < 核心线程数:创建新线程执行任务
  2. 当前线程数 >= 核心线程数:任务加入队列
  3. 队列已满且线程数 < 最大线程数:创建新线程
  4. 队列已满且线程数 = 最大线程数:执行拒绝策略

4.3 常用的拒绝策略

// 1. AbortPolicy:抛出异常(默认)
// 2. CallerRunsPolicy:调用者线程执行
// 3. DiscardPolicy:静默丢弃
// 4. DiscardOldestPolicy:丢弃队列中最旧的任务

// 自定义拒绝策略
public class CustomRejectedHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 记录日志或执行其他操作
        System.err.println("任务被拒绝: " + r.toString());
    }
}

五、Future:获取异步结果

5.1 Callable与Future

Callable接口允许任务返回结果:

import java.util.concurrent.*;

public class FutureExample {
    private final ExecutorService executor = Executors.newFixedThreadPool(5);
    
    public void calculateAsync() throws Exception {
        Future<Integer> future = executor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(1000);
                return 42;
            }
        });
        
        // 执行其他操作...
        
        // 获取结果(阻塞直到任务完成)
        Integer result = future.get();
        System.out.println("结果: " + result);
    }
}

5.2 Future的局限性

传统的Future存在以下问题:

  • 阻塞获取结果:get()方法会阻塞
  • 无法链式调用:不支持任务组合
  • 无法手动完成:不能主动设置结果
  • 缺少异常处理:异常处理不够优雅

这些问题促使Java 8引入了CompletableFuture。

六、CompletableFuture:现代异步编程

6.1 创建CompletableFuture

CompletableFuture提供了多种创建方式:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureBasics {
    // 方式1: 使用runAsync执行无返回值任务
    public CompletableFuture<Void> runAsync() {
        return CompletableFuture.runAsync(() -> {
            System.out.println("异步执行任务");
        });
    }
    
    // 方式2: 使用supplyAsync执行有返回值任务
    public CompletableFuture<String> supplyAsync() {
        return CompletableFuture.supplyAsync(() -> {
            return "异步计算结果";
        });
    }
    
    // 方式3: 手动创建并完成
    public CompletableFuture<String> manualComplete() {
        CompletableFuture<String> future = new CompletableFuture<>();
        // 在某个时刻完成
        future.complete("手动设置的结果");
        return future;
    }
}

6.2 链式操作与转换

CompletableFuture的强大之处在于可以链式组合多个异步操作:

public class CompletableFutureChaining {
    public void chainExample() {
        CompletableFuture.supplyAsync(() -> {
            return "Hello";
        })
        .thenApply(s -> s + " World")  // 转换结果
        .thenApply(String::toUpperCase)  // 继续转换
        .thenAccept(System.out::println)  // 消费结果
        .exceptionally(ex -> {  // 异常处理
            System.err.println("出错了: " + ex.getMessage());
            return null;
        });
    }
}

6.3 组合多个CompletableFuture

public class CompletableFutureCombination {
    // 组合两个独立的异步任务
    public void combineExample() {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
        
        CompletableFuture<String> combined = future1.thenCombine(future2, 
            (s1, s2) -> s1 + " " + s2);
        
        combined.thenAccept(System.out::println);  // 输出: Hello World
    }
    
    // 等待所有任务完成
    public void allOfExample() {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task2");
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task3");
        
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);
        
        allFutures.thenRun(() -> {
            System.out.println("所有任务完成!");
        });
    }
    
    // 任意一个完成即可
    public void anyOfExample() {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            sleep(100);
            return "Fast";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            sleep(200);
            return "Slow";
        });
        
        CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
        anyFuture.thenAccept(result -> 
            System.out.println("最快完成的结果: " + result));
    }
    
    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

6.4 异常处理

CompletableFuture提供了优雅的异常处理机制:

public class CompletableFutureExceptionHandling {
    public void exceptionHandling() {
        CompletableFuture.supplyAsync(() -> {
            if (Math.random() > 0.5) {
                throw new RuntimeException("随机异常");
            }
            return "成功";
        })
        .exceptionally(ex -> {
            // 处理异常并返回默认值
            System.err.println("捕获异常: " + ex.getMessage());
            return "默认值";
        })
        .thenAccept(result -> System.out.println("最终结果: " + result));
    }
    
    public void handleExample() {
        CompletableFuture.supplyAsync(() -> {
            return 10 / 0;  // 会抛出异常
        })
        .handle((result, ex) -> {
            if (ex != null) {
                System.err.println("发生异常: " + ex.getMessage());
                return 0;  // 返回默认值
            }
            return result;
        })
        .thenAccept(result -> System.out.println("结果: " + result));
    }
    
    public void whenCompleteExample() {
        CompletableFuture.supplyAsync(() -> "Hello")
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    System.err.println("异常: " + ex.getMessage());
                } else {
                    System.out.println("成功: " + result);
                }
            });
    }
}

6.5 实战案例:并行数据处理

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class ParallelDataProcessing {
    public CompletableFuture<List<String>> processData(List<Integer> ids) {
        List<CompletableFuture<String>> futures = ids.stream()
            .map(id -> CompletableFuture.supplyAsync(() -> fetchData(id)))
            .map(future -> future.thenApply(this::processData))
            .map(future -> future.thenApply(this::enrichData))
            .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }
    
    private String fetchData(Integer id) {
        // 模拟从数据库或API获取数据
        sleep(100);
        return "Data-" + id;
    }
    
    private String processData(String data) {
        // 模拟数据处理
        return data.toUpperCase();
    }
    
    private String enrichData(String data) {
        // 模拟数据丰富化
        return data + "-ENRICHED";
    }
    
    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

6.6 自定义线程池

默认情况下,CompletableFuture使用ForkJoinPool.commonPool(),但你可以指定自定义线程池:

import java.util.concurrent.*;

public class CustomExecutorExample {
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    public void useCustomExecutor() {
        CompletableFuture.supplyAsync(() -> {
            return "使用自定义线程池";
        }, executor)  // 指定线程池
        .thenApplyAsync(s -> s.toUpperCase(), executor)
        .thenAccept(System.out::println);
    }
    
    public void shutdown() {
        executor.shutdown();
    }
}

七、最佳实践与性能优化

7.1 选择合适的并发工具

场景推荐工具
简单的同步控制synchronized
需要更灵活的锁控制ReentrantLock
读多写少ReadWriteLock
状态标志volatile
批量任务执行线程池
异步任务组合CompletableFuture

7.2 避免常见陷阱

// 陷阱1: 忘记释放锁
Lock lock = new ReentrantLock();
lock.lock();
// 如果这里抛出异常,锁永远不会被释放!
doSomething();
lock.unlock();

// 正确做法
lock.lock();
try {
    doSomething();
} finally {
    lock.unlock();  // 确保锁被释放
}

// 陷阱2: 线程池忘记关闭
ExecutorService executor = Executors.newFixedThreadPool(10);
// 使用线程池...
// 忘记调用shutdown(),线程池会阻止JVM退出

// 正确做法
try {
    // 使用线程池
} finally {
    executor.shutdown();
}

// 陷阱3: CompletableFuture中的阻塞操作
CompletableFuture.supplyAsync(() -> {
    // 不要在这里使用Thread.sleep()或其他阻塞操作
    // 会占用宝贵的ForkJoinPool线程
    return someBlockingOperation();  // 错误!
});

7.3 性能调优建议

  1. 合理设置线程池大小

    • CPU密集型任务:线程数 ≈ CPU核心数 + 1
    • IO密集型任务:线程数 ≈ CPU核心数 × 2
  2. 减少锁的粒度

// 不好:锁的范围太大
public synchronized void process() {
    doSomething1();
    doSomething2();
    criticalSection();
}

// 更好:只锁必要的部分
public void process() {
    doSomething1();
    doSomething2();
    synchronized(this) {
        criticalSection();
    }
}
  1. 使用并发集合
// 不要用synchronized包装普通集合
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

// 使用并发集合性能更好
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

八、总结

Java并发编程从synchronized到CompletableFuture的演进,体现了从简单粗暴的锁机制到优雅的异步编程范式的转变。每种工具都有其适用场景:

  • synchronized:简单场景的首选,JVM优化良好
  • Lock接口:需要更多控制时使用
  • volatile:轻量级的可见性保证
  • 线程池:高效管理线程资源
  • Future:获取异步任务结果
  • CompletableFuture:构建复杂的异步工作流

掌握这些工具,理解它们背后的原理,才能在实际项目中游刃有余地处理并发问题,构建出高性能、高可靠的应用系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值