10、Java核心API系列(八)

九、 并发与多线程

1、Java并发API概述

Java并发API是Java语言中用于多线程编程的核心工具包,主要位于java.util.concurrentjava.util.concurrent.locks包中。它提供了高效、简洁的方式来处理并发编程中的常见问题。

  1. Java并发的核心目标:
    • 提高程序的响应速度。
    • 提高程序的吞吐量(处理更多任务)。
      -简化并发编程的复杂性。
  2. Java并发的基本概念:
    • 线程(Thread): 程序执行的最小单位,负责执行代码。
    • Runnable: 一个接口,定义了run()方法,用于指定线程的执行逻辑。
    • 线程生命周期: 包括新建、就绪、运行、阻塞、死亡等状态。
  3. Java并发API的主要组件:
    • Executor框架: 用于管理线程池和任务执行。
    • Lock接口: 提供更灵活的锁机制。
    • Concurrent集合:ConcurrentHashMapCopyOnWriteArrayList等,适合多线程环境下的数据存储。
    • 原子变量:AtomicIntegerAtomicLong等,用于线程安全地更新单个变量。
  4. Java并发API的优势:
    • 提供了高效的线程池管理。
    • 简化了线程间的通信和同步。
    • 提供了高性能的并发数据结构。

2、线程安全与同步机制

线程安全是并发编程中的核心问题,主要由于共享资源的访问冲突导致。Java提供了多种机制来解决线程安全问题。

  1. 线程安全的原因:

    • 共享变量: 多个线程访问和修改同一个变量,导致数据不一致。
    • 内存可见性: 线程之间的缓存数据不一致。
    • 指令重排: 编译器或处理器对指令的重排导致的逻辑错误。
  2. 同步机制:

    • synchronized关键字:

      • 方法级同步:将方法声明为synchronized,确保同一时间只有一个线程执行该方法。

      • 块级同步:只对指定的代码块加锁,减少锁的粒度。

      • 示例:

        public synchronized void method() {  
            // 同步方法  
        }  
        
    • ReentrantLock

      • 提供了更灵活的锁机制,可以手动加锁和解锁。

      • 示例:

        ReentrantLock lock = new ReentrantLock();  
        lock.lock();  
        try {  
            // 需要同步的代码  
        } finally {  
            lock.unlock();  
        }  
        
  3. volatile关键字:

    • 用于标记变量,确保线程间的可见性和有序性。
    • 不能解决原子性问题。
  4. 线程安全的策略:

    • 避免共享状态: 尽量减少共享变量的使用。
    • 使用不可变对象:StringInteger等。
    • 使用并发集合: 替代传统的集合类。

3、Executor框架(ThreadPoolExecutor)

Executor框架是Java并发API中用于管理线程池的核心工具,简化了线程的创建和管理。

  1. Executor框架的核心接口:

    • Executor: 定义了execute()方法,用于提交任务。
    • ExecutorService: 扩展了Executor,增加了shutdown()invokeAll()等方法。
    • ThreadPoolExecutor: 最常用的线程池实现。
  2. ThreadPoolExecutor的工作原理:

    • 任务提交: 用户通过execute()submit()提交任务。
    • 线程池创建: 根据配置参数(如核心线程数、最大线程数)创建线程。
    • 任务执行: 空闲线程从任务队列中取任务执行。
    • 任务完成: 线程返回线程池,等待下一次任务。
  3. ThreadPoolExecutor的配置参数:

    • corePoolSize: 线程池的核心线程数。
    • maxPoolSize: 线程池的最大线程数。
    • queue: 任务队列,用于存放等待执行的任务。
    • keepAliveTime: 线程空闲的超时时间。
    • threadFactory: 线程工厂,用于创建新线程。
    • handler: 任务拒绝策略,当线程池和队列都满时的处理方式。
  4. ThreadPoolExecutor的使用示例:

    ExecutorService executor = new ThreadPoolExecutor(  
            5,              // 核心线程数  
            10,             // 最大线程数  
            60L,            // 空闲时间  
            TimeUnit.SECONDS,  
            new LinkedBlockingQueue() // 任务队列  
    );  
    
    // 提交任务  
    executor.execute(new Runnable() {  
        @Override  
        public void run() {  
            System.out.println("Task executed by thread: " + Thread.currentThread().getName());  
        }  
    });  
    
    // 关闭线程池  
    executor.shutdown();  
    
  5. 常见的线程池配置:

    • 固定大小线程池: 使用newFixedThreadPool()方法。
    • 缓存线程池: 使用newCachedThreadPool()方法。
    • 单线程线程池: 使用newSingleThreadExecutor()方法。

4、Lock接口与Condition

Lock接口是Java并发API中提供的一种更灵活的锁机制,用于手动加锁和解锁。

  1. Lock接口的核心方法:

    • lock(): 获取锁。
    • unlock(): 释放锁。
    • tryLock(): 尝试获取锁,如果锁被其他线程持有,则返回false
    • tryLock(long time, TimeUnit unit): 在指定时间内尝试获取锁。
  2. Condition接口:

    • ConditionLock接口的补充,用于实现线程间的通信。
    • 核心方法:
      • await(): 使当前线程等待,直到被其他线程唤醒。
      • signal(): 唤醒一个等待的线程。
      • signalAll(): 唤醒所有等待的线程。
  3. Lock与synchronized的对比:

    • Lock:提供更灵活的锁机制,可以中断等待锁的线程。
    • synchronized:自动加锁和解锁,使用简单,但灵活性较低。
  4. 使用示例:

    ReentrantLock lock = new ReentrantLock();  
    Condition condition = lock.newCondition();  
    
    // 线程A  
    lock.lock();  
    try {  
        System.out.println("Thread A: 开始等待");  
        condition.await();  
        System.out.println("Thread A: 被唤醒");  
    } finally {  
        lock.unlock();  
    }  
    
    // 线程B  
    lock.lock();  
    try {  
        Thread.sleep(1000);  
        System.out.println("Thread B: 唤醒线程A");  
        condition.signal();  
    } finally {  
        lock.unlock();  
    }  
    

5、并发集合

并发集合是专门为多线程环境设计的集合类,提供线程安全的数据存取操作。

  1. 常见的并发集合:

    • ConcurrentHashMap: 并发版本的HashMap,支持高效的读写操作。
    • CopyOnWriteArrayList: 并发版本的ArrayList,通过复制数组实现线程安全。
    • BlockingQueue: 支持阻塞的队列操作(如put()take())。
    • ConcurrentLinkedQueue: 高效的并发队列,适合高并发场景。
  2. ConcurrentHashMap的实现原理:

    • 使用分段锁(Segmented Locking)技术,将整个哈希表分成多个段,每个段独立加锁。
    • 读操作不加锁,写操作加锁,提高了读操作的性能。
  3. CopyOnWriteArrayList的实现原理:

    • 每次修改操作(如add()remove())都会创建一个新的数组副本,确保读操作的安全性。
    • 适合读多写少的场景。
  4. 使用示例:

    // 使用ConcurrentHashMap  
    ConcurrentHashMap map = new ConcurrentHashMap<>();  
    map.put("key1", "value1");  
    System.out.println(map.get("key1"));  
    
    // 使用CopyOnWriteArrayList  
    CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();  
    list.add("element1");  
    for (String element : list) {  
        System.out.println(element);  
    }  
    

6、原子变量

原子变量(Atomic Variables)是Java并发API中提供的一组线程安全的单个变量类,用于在多线程环境中安全地更新单个变量。

  1. 常见的原子变量类:

    • AtomicInteger: 原子整数。
    • AtomicLong: 原子长整数。
    • AtomicBoolean: 原子布尔值。
    • AtomicReference: 原子引用。
  2. 原子变量的工作原理:

    • 使用CAS(Compare-And-Swap)算法,确保变量的更新是原子性的。
    • CAS的逻辑:检查当前值是否等于预期值,如果是,则更新为新值。
  3. 使用示例:

    AtomicInteger atomicInt = new AtomicInteger(0);  
    
    // 安全地增加1  
    int oldValue = atomicInt.get();  
    int newValue = atomicInt.compareAndSet(oldValue, oldValue + 1) ? oldValue + 1 : oldValue;  
    System.out.println("New value: " + newValue);  
    
  4. 原子变量的优势:

    • 提供线程安全的更新操作。
    • synchronizedLock更高效。

7、多线程最佳实践

编写高效、可靠的多线程程序需要遵循一些最佳实践。

  1. 避免共享状态:
    • 尽量减少共享变量的使用,使用局部变量或不可变对象。
  2. 使用不可变对象:
    • 不可变对象(如StringInteger)在多线程环境中是线程安全的。
  3. 正确使用同步机制:
    • 使用synchronizedLock保护共享资源。
    • 避免过度同步,减少锁竞争。
  4. 避免过度创建线程:
    • 使用线程池(如ThreadPoolExecutor)管理线程,避免频繁创建和销毁线程。
  5. 正确处理中断:
    • 使用Thread.interrupted()检查线程是否被中断。
    • 在循环中处理中断异常,避免无限循环。
  6. 使用volatile关键字:
    • 确保线程间的可见性和有序性。
  7. 避免使用Thread.stop():
    • Thread.stop()已被废弃,使用interrupt()方法中断线程。
  8. 正确关闭线程池:
    • 使用shutdown()方法关闭线程池,避免直接调用System.exit()
  9. 使用并发集合:
    • 替代传统的集合类,确保线程安全。
  10. 测试并发程序:
    • 多线程程序的逻辑错误难以复现,需要充分测试。

8、综合案例:高并发秒杀系统设计

以下是一个综合案例,展示了Java并发与多线程的多个知识点在实际场景中的应用。这是一个模拟电商平台的秒杀系统,旨在解决高并发场景下的线程安全、任务队列管理、并发控制等问题。


案例背景

电商平台上一个秒杀活动需要处理大量用户的并发请求。要求在有限时间内(如1秒)处理成千上万的请求,同时确保库存安全、数据一致性和系统稳定性。


实现目标
  1. 高并发处理: 使用线程池管理大量并发请求。
  2. 库存安全: 确保库存不能为负数。
  3. 任务队列: 使用阻塞队列管理秒杀请求。
  4. 线程安全: 使用并发集合和锁机制保障数据一致性。
  5. 秒杀控制: 支持秒杀开始和结束的精确控制。

实现思路
  1. 线程池管理: 使用ThreadPoolExecutor创建固定大小的线程池,处理秒杀请求。
  2. 秒杀执行器: 使用ReentrantLockCondition控制秒杀的开始和结束。
  3. 库存管理: 使用ConcurrentHashMap记录用户秒杀请求和库存。
  4. 阻塞队列: 使用LinkedBlockingQueue管理秒杀请求。
  5. 原子变量: 使用AtomicInteger记录秒杀剩余数量。

代码实现
import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.ConcurrentHashMap;  
import java.util.concurrent.CopyOnWriteArrayList;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.LinkedBlockingQueue;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.atomic.AtomicInteger;  
import java.util.logging.Logger;  

public class SecKillSystem {  

    private static final Logger LOGGER = Logger.getLogger(SecKillSystem.class.getName());  
    // 秒杀执行器  
    private final ExecutorService executor;  
    // 用于控制秒杀的开始和结束  
    private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock();  
    private final java.util.concurrent.locks.Condition condition = lock.newCondition();  
    // 秒杀商品池  
    private final CopyOnWriteArrayList goodsPool = new CopyOnWriteArrayList<>();  
    // 用户秒杀请求队列  
    private final BlockingQueue requestQueue = new LinkedBlockingQueue<>();  
    // 用户秒杀结果记录  
    private final ConcurrentHashMap resultMap = new ConcurrentHashMap<>();  
    // 剩余库存(原子变量)  
    private final AtomicInteger remainingStock = new AtomicInteger(0);  

    public SecKillSystem(int threadPoolSize) {  
        this.executor = Executors.newFixedThreadPool(threadPoolSize);  
    }  

    // 初始化秒杀商品  
    public void initSecKill(Goods goods) {  
        goodsPool.add(goods);  
        remainingStock.set(goods.getStock());  
        LOGGER.info("Initialized secKill goods: " + goods.getName() + ", stock: " + goods.getStock());  
    }  

    // 开始秒杀  
    public void startSecKill() {  
        lock.lock();  
        try {  
            LOGGER.info("Starting secKill...");  
            condition.signalAll(); // 唤醒所有等待的线程  
        } finally {  
            lock.unlock();  
        }  
    }  

    // 提交秒杀请求  
    public void submitRequest(User user, Goods goods) {  
        try {  
            requestQueue.put(new UserRequest(user, goods));  
            LOGGER.info("User " + user.getId() + " submitted request for " + goods.getName());  
        } catch (InterruptedException e) {  
            LOGGER.warning("Interrupted while submitting request: " + e.getMessage());  
            Thread.currentThread().interrupt();  
        }  
    }  

    // 处理秒杀请求  
    private void processRequests() {  
        executor.execute(() -> {  
            while (true) {  
                try {  
                    UserRequest request = requestQueue.take(); // 阻塞获取请求  
                    if (isSecKillActive()) {  
                        handleRequest(request);  
                    } else {  
                        LOGGER.info("SecKill is not active, rejecting request");  
                        resultMap.put(request.getUser().getId(), false);  
                    }  
                } catch (InterruptedException e) {  
                    LOGGER.warning("Interrupted while processing requests: " + e.getMessage());  
                    Thread.currentThread().interrupt();  
                }  
            }  
        });  
    }  

    // 检查秒杀是否活跃  
    private boolean isSecKillActive() {  
        lock.lock();  
        try {  
            return remainingStock.get() > 0;  
        } finally {  
            lock.unlock();  
        }  
    }  

    // 处理单个秒杀请求  
    private void handleRequest(UserRequest request) {  
        Goods goods = request.getGoods();  
        if (goods.getStock() <= 0) {  
            LOGGER.info("Goods " + goods.getName() + " is sold out");  
            resultMap.put(request.getUser().getId(), false);  
            return;  
        }  

        try {  
            boolean success = goods.decreaseStock();  
            if (success) {  
                LOGGER.info("User " + request.getUser().getId() + " successfully got " + goods.getName());  
                resultMap.put(request.getUser().getId(), true);  
            } else {  
                LOGGER.info("Failed to process request for user " + request.getUser().getId());  
                resultMap.put(request.getUser().getId(), false);  
            }  
        } catch (Exception e) {  
            LOGGER.severe("Error processing request: " + e.getMessage());  
            resultMap.put(request.getUser().getId(), false);  
        }  
    }  

    // 关闭秒杀系统  
    public void shutDown() {  
        executor.shutdown();  
        try {  
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {  
                LOGGER.warning("Executor did not terminate");  
            }  
        } catch (InterruptedException e) {  
            LOGGER.warning("Interrupted while shutting down: " + e.getMessage());  
            Thread.currentThread().interrupt();  
        }  
    }  

    public static class Goods {  
        private final String name;  
        private final AtomicInteger stock;  

        public Goods(String name, int stock) {  
            this.name = name;  
            this.stock = new AtomicInteger(stock);  
        }  

        public String getName() {  
            return name;  
        }  

        public AtomicInteger getStock() {  
            return stock;  
        }  

        public boolean decreaseStock() {  
            int currentStock = stock.get();  
            if (currentStock <= 0) {  
                return false;  
            }  
            return stock.compareAndSet(currentStock, currentStock - 1);  
        }  
    }  

    public static class User {  
        private final String id;  

        public User(String id) {  
            this.id = id;  
        }  

        public String getId() {  
            return id;  
        }  
    }  

    public static class UserRequest {  
        private final User user;  
        private final Goods goods;  

        public UserRequest(User user, Goods goods) {  
            this.user = user;  
            this.goods = goods;  
        }  

        public User getUser() {  
            return user;  
        }  

        public Goods getGoods() {  
            return goods;  
        }  
    }  

    public static void main(String[] args) {  
        // 初始化秒杀系统  
        SecKillSystem secKillSystem = new SecKillSystem(10); // 创建10个线程的线程池  

        // 创建商品  
        Goods iPhone = new Goods("iPhone 15", 100);  
        Goods macBook = new Goods("MacBook Pro", 50);  

        // 初始化商品到秒杀系统  
        secKillSystem.initSecKill(iPhone);  
        secKillSystem.initSecKill(macBook);  

        // 模拟用户提交请求  
        for (int i = 1; i <= 200; i++) {  
            User user = new User("user_" + i);  
            Goods goods = (i % 2 == 0) ? macBook : iPhone;  
            secKillSystem.submitRequest(user, goods);  
        }  

        // 开始秒杀  
        secKillSystem.startSecKill();  

        // 关闭秒杀系统  
        try {  
            Thread.sleep(5000); // 等待5秒  
            secKillSystem.shutDown();  
        } catch (InterruptedException e) {  
            LOGGER.warning("Interrupted while waiting for secKill to complete: " + e.getMessage());  
            Thread.currentThread().interrupt();  
        }  

        // 输出结果  
        LOGGER.info("SecKill results: " + secKillSystem.resultMap);  
    }  
}  

案例分析
  1. 线程池管理(ExecutorService):
    • 使用ThreadPoolExecutor创建固定大小的线程池,处理秒杀请求。
    • 主线程负责提交任务,子线程负责执行秒杀逻辑。
  2. 锁与条件(ReentrantLock & Condition):
    • 控制秒杀的开始和结束。
    • 使用Condition实现线程间通信,唤醒等待的线程。
  3. 阻塞队列(BlockingQueue):
    • 使用LinkedBlockingQueue管理秒杀请求,实现线程安全的队列操作。
  4. 并发集合(ConcurrentHashMap & CopyOnWriteArrayList):
    • 使用ConcurrentHashMap记录秒杀结果,确保线程安全。
    • 使用CopyOnWriteArrayList管理秒杀商品,适合读多写少的场景。
  5. 原子变量(AtomicInteger):
    • 用于记录秒杀剩余库存,确保原子性和可见性。
  6. 最佳实践:
    • 避免共享状态:通过局部变量和不可变对象减少共享变量。
    • 使用volatile关键字:确保线程间的可见性和有序性。
    • 正确处理中断:在try-catch块中处理中断异常,避免无限循环。

你可能感兴趣的:(java,开发语言)