Java进阶——一篇搞懂多线程实战

        本文主要是对 Java 多线程相关知识进行总结和讲解。

一、 线程创建方式

  • 继承 Thread:通过重写 run() 方法来定义线程的执行逻辑,只能单继承。
  • 实现 Runnable 接口:这种方式更加灵活,避免了单继承的问题,而且同一个 Runnable 实例可以被多个线程共享,提高了代码的复用性,因此在实际开发中更为推荐。
  • 实现 Callable 接口:与前两种方式不同,Callable 接口支持线程执行后返回结果,并且可以抛出异常。不过,使用 Callable 接口需要配合 FutureTask 或者线程池来使用。

用户下单后异步发送通知:

// 实现Runnable接口
public class OrderNotifyTask implements Runnable {
    private final Order order;
    
    public OrderNotifyTask(Order order) {
        this.order = order;
    }
    
    @Override
    public void run() {
        // 发送短信/邮件通知
        NotificationService.send(order.getUserId(), "您的订单已创建");
    }
}

// 使用线程池提交任务
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(new OrderNotifyTask(order));



二、 线程生命周期

Java 线程的生命周期包含 6 种状态,这些状态由 Thread.State 枚举定义:

  • NEW(新建):线程对象被创建,但尚未启动。
  • RUNNABLE(可运行):线程正在 Java 虚拟机中执行,或者正在等待系统资源(如 CPU 时间片)以执行。
  • BLOCKED(阻塞):线程因为等待获取锁而被阻塞。
  • WAITING(无限等待):线程进入无限期等待状态,直到其他线程唤醒它。
  • TIMED_WAITING(超时等待):线程在指定的时间内等待,超时后自动唤醒。
  • TERMINATED(终止):线程执行完毕或者因异常而终止。
    Java进阶——一篇搞懂多线程实战_第1张图片


三、线程同步与锁

  • synchronized 关键字
    • 可以修饰实例方法,此时锁对象为该实例;修饰静态方法时,锁对象为类对象;还可以修饰代码块,需要显式指定锁对象。
    • JVM 对 synchronized 进行了优化,引入了锁升级机制,从无锁状态逐步升级为偏向锁、轻量级锁,最终变为重量级锁。
  • volatile 关键字
    • 保证变量的可见性,即一个线程对变量的修改对其他线程是立即可见的。
    • 禁止指令重排,确保程序执行的顺序性。但需要注意的是,volatile 不保证原子性,例如 i++ 这样的复合操作就需要配合锁或者原子类来保证原子性。
  • 显式锁 ReentrantLock
    • 支持公平锁,即按照线程请求锁的顺序来分配锁;支持可中断锁,允许线程在等待锁的过程中被中断;还支持超时获取锁,避免线程无限期等待。
    • 提供了多条件变量(Condition),可以实现更复杂的线程同步逻辑。

锁在库存扣减中的使用:

public synchronized boolean deductStock(Long itemId, int quantity) {
        int current = inventory.getOrDefault(itemId, 0);
        if (current < quantity) return false;
        inventory.put(itemId, current - quantity);
        return true;
    }
}



四、 线程间通信

  • wait()notify()
    • 这两个方法必须在 synchronized 块中使用,否则会抛出 IllegalMonitorStateException 异常。
    • wait() 方法会使线程释放锁并进入等待状态,而 sleep() 方法不会释放锁。
  • LockCondition
    • 通过 Condition.await()Condition.signal() 方法可以实现更灵活的等待/唤醒机制,相比 wait()notify() 更加灵活。



五、 并发工具类

  • CountDownLatch:用于等待多个线程完成任务,是一次性的,一旦计数器减为 0,就无法再次使用。
  • CyclicBarrier:多个线程相互等待,直到所有线程都到达屏障点后再继续执行,并且可以重复使用。
  • Semaphore:用于控制并发线程的数量,起到限流的作用。

常见场景如下:

public class OrderPaymentService {
    public void handlePaymentSuccess(Order order) {
        CompletableFuture.runAsync(() -> updateOrderStatus(order))
            .thenRunAsync(() -> sendPaymentNotify(order))
            .thenRunAsync(() -> grantCoupon(order.getUserId()))
            .exceptionally(ex -> {
                log.error("支付后处理异常", ex);
                return null;
            });
    }
    
    // 使用CountDownLatch等待多个服务响应
    public boolean checkSystemStatus() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        executor.submit(() -> { checkDB(); latch.countDown(); });
        executor.submit(() -> { checkCache(); latch.countDown(); });
        executor.submit(() -> { checkMQ(); latch.countDown(); });
        return latch.await(5, TimeUnit.SECONDS);
    }
}



六、 线程池

  • 核心参数:包括核心线程数、最大线程数、线程存活时间、阻塞队列以及拒绝策略。
  • 工作流程:当任务提交时,首先由核心线程处理;如果核心线程已满,则任务进入阻塞队列;当队列也满时,会创建非核心线程来处理任务;如果线程数达到最大线程数且队列已满,则会触发拒绝策略。
  • 拒绝策略:常见的有 AbortPolicy(抛出异常)、CallerRunsPolicy(由调用者线程执行任务)、DiscardPolicy(静默丢弃任务)。
  • 常见线程池
    • FixedThreadPool:固定线程数的线程池。
    • CachedThreadPool:弹性线程数的线程池,但可能会导致内存溢出(OOM)。
    • SingleThreadExecutor:单线程串行执行的线程池。

推荐手动创建 ThreadPoolExecutor,以避免使用 Executors 工厂类创建线程池可能带来的资源耗尽问题。



← 上一篇 Java进阶——常用工具类
记得点赞、关注、收藏哦!
下一篇 Java进阶——IO流实战详解 →

你可能感兴趣的:(Java,java)