Java 多线程编程:核心原理、同步机制与高并发详解

目录

一、核心概念

[1. 进程 vs 线程](#1. 进程 vs 线程)

[2. 并行 vs 并发](#2. 并行 vs 并发)

[3. 线程状态](#3. 线程状态)

[4. 状态转换图](#4. 状态转换图)

二、线程创建与启动

[1、继承 Thread 类](#1、继承 Thread 类)

[2、实现 Runnable 接口](#2、实现 Runnable 接口)

[3、使用 Callable 和 Future](#3、使用 Callable 和 Future)

4、三种方式的对比

三、线程安全与同步机制

[1. 线程安全问题](#1. 线程安全问题)

[2. synchronized 关键字](#2. synchronized 关键字)

[3. ReentrantLock(JUC 包)](#3. ReentrantLock(JUC 包))

[4. 原子类(AtomicXXX)](#4. 原子类(AtomicXXX))

四、线程间协作

[1. wait()/notify()/notifyAll()](#1. wait()/notify()/notifyAll())

[2. Condition 接口](#2. Condition 接口)

五、线程池(ExecutorService)

[1. 线程池的优势](#1. 线程池的优势)

2.线程池的五种状态

[3. 线程池状态转换](#3. 线程池状态转换)

4、核心类:ThreadPoolExecutor

5、线程池的创建方式

[1. 使用 Executors 工厂类(简化但有局限性)](#1. 使用 Executors 工厂类(简化但有局限性))

FixedThreadPool(固定大小线程池):

CachedThreadPool(可缓存线程池):

ScheduledThreadPool(定时任务线程池):

SingleThreadExecutor(单线程池):

[2. 自定义 ThreadPoolExecutor(推荐)](#2. 自定义 ThreadPoolExecutor(推荐))

3、拒绝策略(RejectedExecutionHandler)

4、自定义策略

5、线程池的生命周期管理

6.最佳实践与注意事项

[1. 线程数配置策略](#1. 线程数配置策略)

[2. 避免使用无界队列](#2. 避免使用无界队列)

[3. 优先使用线程池而非手动创建线程](#3. 优先使用线程池而非手动创建线程)

[4. 异常处理](#4. 异常处理)

7、典型应用场景

[六、高级工具类(JUC 包)](#六、高级工具类(JUC 包))

[1. CountDownLatch](#1. CountDownLatch)

[2. CyclicBarrier](#2. CyclicBarrier)

[3. Semaphore](#3. Semaphore)

七、线程安全集合类

[1. 传统集合的线程安全问题](#1. 传统集合的线程安全问题)

[2. JUC 包中的线程安全集合](#2. JUC 包中的线程安全集合)

八、多线程设计模式

[1. 生产者 - 消费者模式](#1. 生产者 - 消费者模式)

[2. 单例模式(双重检查锁)](#2. 单例模式(双重检查锁))

九、线程调试与性能优化

[1. 线程调试工具](#1. 线程调试工具)

[2. 性能优化建议](#2. 性能优化建议)

十、注意事项


一、核心概念

1. 进程 vs 线程
  • 进程:程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。
  • 线程:进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程。
2. 并行 vs 并发
  • 并行:多个任务在同一时刻同时执行(多核 CPU)。
  • 并发:多个任务在同一时间段内交替执行(单核 CPU 通过时间片切换实现)。
3. 线程状态

Java 线程有 6 种状态(Thread.State):

状态名称 描述
NEW 线程对象已创建,但尚未调用start()方法。
RUNNABLE 线程正在 Java 虚拟机中执行,或者准备就绪等待 CPU 时间片。
BLOCKED 线程正在等待获取监视器锁(如synchronized块),处于阻塞状态。
WAITING 线程无限期等待另一个线程执行特定操作(如调用wait()join())。
TIMED_WAITING 线程在指定时间内等待另一个线程执行操作(如Thread.sleep())。
TERMINATED 线程已执行完毕,终止运行。
4. 状态转换图

二、线程创建与启动

在 Java 中,创建线程主要有三种方式,分别是继承Thread类、实现Runnable接口以及使用CallableFuture。下面将详细介绍这三种方式及其区别。

1、继承 Thread 类

步骤

  1. 定义一个类继承自Thread类。
  2. 重写run()方法,在该方法中定义线程要执行的任务。
  3. 创建该类的实例,并调用start()方法启动线程。

示例代码

复制代码
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类创建的线程正在执行,线程名:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程执行完毕");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
        System.out.println("主线程继续执行");
    }
}

特点

  • 优点:实现简单,直接通过继承Thread类并重写run()方法即可。
  • 缺点:由于 Java 是单继承的,继承了Thread类后就无法再继承其他类,会导致类的扩展性受限。
2、实现 Runnable 接口

步骤

  1. 定义一个类实现Runnable接口。
  2. 实现run()方法,在该方法中定义线程要执行的任务。
  3. 创建该类的实例,并将其作为参数传递给Thread类的构造函数。
  4. 调用Thread实例的start()方法启动线程。

示例代码

复制代码
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runnable接口创建的线程正在执行,线程名:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程执行完毕");
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start(); // 启动线程
        System.out.println("主线程继续执行");
    }
}

特点

优点:避免了单继承的限制,一个类可以在实现Runnable接口的同时继承其他类,提高了类的扩展性。适合多个线程共享同一个资源的场景,例如多个线程同时操作同一个银行账户。

  • 缺点:代码相对复杂一些,需要创建Runnable实现类的实例并将其传递给Thread类。
3、使用 Callable 和 Future

步骤

  1. 定义一个类实现Callable接口,指定返回值的类型。
  2. 实现call()方法,在该方法中定义线程要执行的任务,并返回结果。
  3. 创建ExecutorService线程池。
  4. 调用线程池的submit()方法提交Callable任务,并获取Future对象。
  5. 调用Future对象的get()方法获取线程执行的结果。

示例代码

复制代码
import java.util.concurrent.*;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("实现Callable接口创建的线程正在执行,线程名:" + Thread.currentThread().getName());
        Thread.sleep(1000);
        return 100; // 返回计算结果
    }

    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // 提交Callable任务
        Future<Integer> future = executor.submit(new MyCallable());
        
        try {
            // 获取线程执行结果(可能会阻塞,直到任务完成)
            Integer result = future.get();
            System.out.println("线程执行结果:" + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            executor.shutdown();
        }
        System.out.println("主线程继续执行");
    }
}

特点

  • 优点:Callable接口的call()方法可以返回结果,并且可以抛出异常,这是Runnable接口所不具备的功能。通过Future对象可以方便地获取线程的执行结果,还可以检查线程是否完成、取消线程等操作。
  • 缺点:代码复杂度较高,需要使用线程池来执行任务,适合需要返回结果的异步任务场景。
4、三种方式的对比
方式 继承 Thread 类 实现 Runnable 接口 实现 Callable 接口
启动方式 直接调用start()方法 需要创建Thread对象 需要通过线程池执行
是否支持返回结果 是(通过Future获取)
是否支持异常抛出
是否受单继承限制
适用场景 简单的线程任务 共享资源的多线程任务 需要返回结果的异步任务
  • 如果需要线程执行的任务简单,且不需要返回结果,可以选择继承Thread类的方式。
  • 如果需要多个线程共享同一个资源,或者为了避免单继承的限制,建议选择实现Runnable接口的方式。
  • 如果线程执行的任务需要返回结果,或者需要抛出异常,那么应该选择实现Callable接口的方式,并结合线程池使用。

三、线程安全与同步机制

1. 线程安全问题

当多个线程同时访问共享资源时,可能出现以下问题:

  • 竞态条件(Race Condition):多个线程对共享数据的读写操作导致结果不确定。
  • 内存可见性问题:一个线程修改了共享变量,其他线程可能看不到最新值。
2. synchronized 关键字
  • 同步方法

    复制代码
    public synchronized void increment() {
        count++;
    }
  • 同步代码块

    复制代码
    public void increment() {
        synchronized (this) {
            count++;
        }
    }
3. ReentrantLock(JUC 包)
复制代码
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
4. 原子类(AtomicXXX)
复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // 原子操作
    }
}

四、线程间协作

1. wait()/notify()/notifyAll()
复制代码
public class ProducerConsumer {
    private final Object lock = new Object();
    private int data = 0;
    private boolean available = false;

    public void produce(int value) throws InterruptedException {
        synchronized (lock) {
            while (available) {
                lock.wait(); // 等待消费者取走数据
            }
            data = value;
            available = true;
            lock.notifyAll(); // 通知消费者数据已就绪
        }
    }

    public int consume() throws InterruptedException {
        synchronized (lock) {
            while (!available) {
                lock.wait(); // 等待生产者生产数据
            }
            available = false;
            lock.notifyAll(); // 通知生产者数据已取走
            return data;
        }
    }
}
2. Condition 接口
复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final java.util.LinkedList<Integer> buffer = new java.util.LinkedList<>();
    private static final int MAX_SIZE = 10;

    public void put(int value) throws InterruptedException {
        lock.lock();
        try {
            while (buffer.size() == MAX_SIZE) {
                notFull.await(); // 缓冲区满,等待
            }
            buffer.add(value);
            notEmpty.signal(); // 通知消费者
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (buffer.isEmpty()) {
                notEmpty.await(); // 缓冲区空,等待
            }
            int value = buffer.poll();
            notFull.signal(); // 通知生产者
            return value;
        } finally {
            lock.unlock();
        }
    }
}

五、线程池(ExecutorService)

线程池可以对线程进行统一管理和复用,避免频繁创建和销毁线程带来的性能开销。Java 通过**ExecutorService接口** 和**Executors工厂类**来创建线程池。

1. 线程池的优势
  • 降低资源消耗
    避免频繁创建和销毁线程,重复利用已创建的线程。
  • 控制并发数量
    通过设置核心线程数、最大线程数等参数,防止因线程过多导致的资源竞争和阻塞。
  • 简化线程管理
    提供统一的任务提交接口(如submit()),并支持任务优先级、定时任务等高级功能。
  • 提高响应速度
    线程池中的线程可直接复用,无需等待新线程创建。
2.线程池的五种状态
  1. RUNNING(运行中)
  • 状态值111(二进制,高 3 位)。
  • 含义
    • 线程池正常运行,接受新任务,并处理阻塞队列中的任务。
    • 初始状态即为 RUNNING
  • 转换来源
    • 新建线程池时自动进入此状态。
  1. SHUTDOWN(关闭中)
  • 状态值000(二进制,高 3 位)。
  • 含义
    • 不再接受新任务 ,但会继续处理阻塞队列中已有的任务
    • 调用 shutdown() 方法会触发此状态转换。
  • 转换来源
    • RUNNING 状态调用 shutdown()
  1. STOP(停止中)
  • 状态值001(二进制,高 3 位)。
  • 含义
    • 立即停止线程池:
      • 中断所有正在执行的任务
      • 丢弃阻塞队列中未处理的任务
    • 调用 shutdownNow() 方法会触发此状态转换。
  • 转换来源
    • RUNNINGSHUTDOWN 状态调用 shutdownNow()
  1. TIDYING(整理中,过渡状态)
  • 状态值010(二进制,高 3 位)。
  • 含义
    • 所有任务(包括正在执行的和队列中的)已终止,工作线程数为 0
    • 即将进入 TERMINATED 状态前的过渡状态。
  • 触发条件
    • SHUTDOWNSTOP 状态下,任务队列和工作线程均为空时,自动进入此状态。
  1. TERMINATED(终止)
  • 状态值011(二进制,高 3 位)。
  • 含义
    • 线程池已完全终止,所有资源已释放。
  • 触发条件
    • TIDYING 状态下,执行完终止钩子函数(terminated() 方法)后,进入此状态。
3. 线程池状态转换
4、核心类:ThreadPoolExecutor

Java 线程池的核心实现类是ThreadPoolExecutor,通过构造方法可自定义参数,灵活控制线程池行为。

构造方法参数详解:

复制代码
public ThreadPoolExecutor(
    int corePoolSize,          // 核心线程数:线程池长期维持的最小线程数,即使空闲也不会销毁
    int maximumPoolSize,       // 最大线程数:线程池允许创建的最大线程数
    long keepAliveTime,        // 存活时间:非核心线程空闲时的存活时长
    TimeUnit unit,             // 时间单位
    BlockingQueue<Runnable> workQueue, // 任务队列:存放待执行的任务
    ThreadFactory threadFactory, // 线程工厂:创建线程的工厂(可自定义线程名、优先级等)
    RejectedExecutionHandler handler // 拒绝策略:任务无法执行时的处理逻辑
)

参数配置逻辑:

  1. 任务提交流程

    • 当线程数 < corePoolSize:创建新线程执行任务。
    • 当线程数 ≥ corePoolSize:任务加入workQueue队列。
    • 当队列已满且线程数 < maximumPoolSize:创建非核心线程执行任务。
    • 当队列已满且线程数 ≥ maximumPoolSize:触发拒绝策略
  2. 队列类型选择

    • 有界队列(如 ArrayBlockingQueue):避免内存溢出,需配合拒绝策略使用。
    • 无界队列(如 LinkedBlockingQueue)maximumPoolSize参数失效(线程数不超过corePoolSize),可能导致 OOM。
    • 优先队列(如 PriorityBlockingQueue):按任务优先级执行。
5、线程池的创建方式
1. 使用 Executors 工厂类(简化但有局限性)
FixedThreadPool(固定大小线程池)
复制代码
  ExecutorService fixedPool = Executors.newFixedThreadPool(5); // corePoolSize = maxPoolSize = 5,使用无界队列

适用场景 :任务量已知、负载均衡的场景(如数据库连接池)。
风险:无界队列可能导致内存溢出。
*

CachedThreadPool(可缓存线程池)
复制代码
  ExecutorService cachedPool = Executors.newCachedThreadPool(); // corePoolSize=0,maxPoolSize=Integer.MAX_VALUE

特点 :线程空闲 60 秒后销毁,适合短时间任务(如 HTTP 请求处理)。
风险:线程数可能无限增长,导致 OOM。
*

ScheduledThreadPool(定时任务线程池)
复制代码
  ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
  // 延迟1秒执行任务
  scheduledPool.schedule(() -> System.out.println("延迟执行"), 1, TimeUnit.SECONDS);

适用场景:定时任务、周期性任务(如日志上报)。
*

SingleThreadExecutor(单线程池)
复制代码
  ExecutorService singlePool = Executors.newSingleThreadExecutor(); // 保证任务按顺序执行

适用场景:需要保证任务顺序执行或串行执行的场景。

2. 自定义 ThreadPoolExecutor(推荐)
复制代码
// 自定义有界队列和拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,                          // 核心线程数
    5,                          // 最大线程数
    30,                         // 非核心线程存活时间(秒)
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10), // 有界队列(容量10)
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用者线程执行任务
);
3、拒绝策略(RejectedExecutionHandler)

当任务无法被线程池处理时(队列已满且线程数达上限),触发拒绝策略,共有 4 种内置策略:

策略名称 行为描述
AbortPolicy(默认) 抛出RejectedExecutionException,终止任务执行(需手动捕获异常)。
CallerRunsPolicy 由提交任务的线程(如主线程)直接执行任务,降低提交任务的速度。
DiscardPolicy 静默丢弃无法处理的任务,不抛出异常(可能丢失数据)。
DiscardOldestPolicy 丢弃队列中最旧的任务,尝试提交当前任务(可能导致重要任务被丢弃)。
4、自定义策略
复制代码
class CustomRejectedHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义处理逻辑(如记录日志、写入数据库)
        System.out.println("任务被拒绝:" + r.toString());
    }
}
5、线程池的生命周期管理
  1. 关闭线程池

    • shutdown():平滑关闭,不再接受新任务,但会等待已提交的任务执行完毕。

    • shutdownNow():立即关闭,尝试中断正在执行的任务,并返回等待执行的任务列表。

      executor.shutdown(); // 推荐使用此方式,避免任务丢失
      try {
      if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // 等待60秒
      executor.shutdownNow(); // 超时后强制关闭
      }
      } catch (InterruptedException e) {
      executor.shutdownNow();
      }

6.最佳实践与注意事项
1. 线程数配置策略
  • 计算密集型任务
    线程数 = CPU 核心数(如Runtime.getRuntime().availableProcessors()),避免上下文切换开销。
  • IO 密集型任务
    线程数 = CPU 核心数 × 2(或更高),因 IO 等待时线程可释放 CPU 资源。
    公式参考:线程数 = CPU 核心数 × (1 + 平均等待时间 / 平均工作时间)
  • 通用原则:
    健壮的线程池设计 = 有界队列 + 明确拒绝策略 + 合理线程数
2. 避免使用无界队列

Executors.newFixedThreadPool()newSingleThreadExecutor()默认使用LinkedBlockingQueue(无界队列),可能导致内存耗尽,建议自定义有界队列:

复制代码
// 错误示例(无界队列)
ExecutorService badPool = Executors.newFixedThreadPool(5); 

// 正确示例(有界队列+拒绝策略)
ExecutorService goodPool = new ThreadPoolExecutor(
    5, 5, 0, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(100), // 限制队列容量
    new ThreadPoolExecutor.AbortPolicy()
);
3. 优先使用线程池而非手动创建线程
复制代码
// 推荐:使用线程池复用线程
executor.submit(() -> processTask());

// 不推荐:手动创建线程(缺乏管理)
new Thread(() -> processTask()).start();
4. 异常处理
  • 任务中抛出的未捕获异常会导致线程终止,需在run()call()中捕获异常,或通过Future处理:

    复制代码
    Future<?> future = executor.submit(() -> {
        if (hasError()) {
            throw new RuntimeException("任务失败"); // 需通过future.get()捕获ExecutionException
        }
    });
    try {
        future.get(); // 捕获异常
    } catch (ExecutionException e) {
        handleError(e.getCause());
    }
7、典型应用场景
  1. Web 服务器请求处理
    每个 HTTP 请求作为任务提交到线程池,避免为每个请求创建新线程。
  2. 异步数据处理
    批量处理数据(如日志解析、文件上传),利用线程池并行加速。
  3. 定时任务调度
    使用ScheduledThreadPoolExecutor实现定时统计、缓存更新等功能。
  4. 分布式系统中的任务队列
    结合消息中间件(如 Kafka),使用线程池消费消息,控制并发量。

六、高级工具类(JUC 包)

1. CountDownLatch
复制代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int taskId = i;
            new Thread(() -> {
                System.out.println("任务" + taskId + "开始执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskId + "执行完毕");
                latch.countDown(); // 计数器减1
            }).start();
        }

        latch.await(); // 主线程等待所有任务完成
        System.out.println("所有任务已完成");
    }
}
2. CyclicBarrier
复制代码
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int parties = 3;
        CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
            System.out.println("所有线程已到达屏障");
        });

        for (int i = 0; i < parties; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    System.out.println("线程" + threadId + "正在准备");
                    Thread.sleep(1000);
                    System.out.println("线程" + threadId + "已准备好,等待其他线程");
                    barrier.await(); // 等待其他线程
                    System.out.println("线程" + threadId + "继续执行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
3. Semaphore
复制代码
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        int permits = 2; // 允许2个线程同时访问
        Semaphore semaphore = new Semaphore(permits);

        for (int i = 0; i < 5; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 获取许可
                    System.out.println("线程" + threadId + "获取到许可,开始执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放许可
                    System.out.println("线程" + threadId + "释放许可");
                }
            }).start();
        }
    }
}

七、线程安全集合类

1. 传统集合的线程安全问题
复制代码
// 非线程安全的集合
List<String> list = new ArrayList<>();

// 线程安全的替代方案
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
2. JUC 包中的线程安全集合
复制代码
// 高效的线程安全队列
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();

// 并发Map(分段锁实现)
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

八、多线程设计模式

1. 生产者 - 消费者模式

使用BlockingQueue实现:

复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducerConsumerPattern {
    private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

    public static void main(String[] args) {
        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.put(i);
                    System.out.println("生产:" + i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    Integer item = queue.take();
                    System.out.println("消费:" + item);
                    if (item == 9) break; // 结束条件
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }
}
2. 单例模式(双重检查锁)
复制代码
public class Singleton {
    private static volatile Singleton instance; // 使用volatile保证可见性

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

九、线程调试与性能优化

1. 线程调试工具
  • jstack:打印线程堆栈信息,用于分析死锁和阻塞问题。
  • VisualVM:可视化监控工具,查看线程状态、CPU 使用率等。
  • ThreadMXBean:编程方式获取线程信息。
2. 性能优化建议
  • 减少锁的粒度,避免synchronized块过大。

  • 使用无锁数据结构(如ConcurrentHashMap)替代传统同步集合。

  • 合理配置线程池大小:

    复制代码
    计算密集型任务:线程数 = CPU核心数 + 1
    IO密集型任务:线程数 = CPU核心数 * 2

十、注意事项

  1. 避免死锁

    1. 按相同顺序获取锁。

    2. 设置锁获取超时时间。

  2. 线程池使用规范

    1.避免使用无界队列(如Executors.newFixedThreadPool)。

    1. 明确拒绝策略(如CallerRunsPolicy)。
  3. 内存泄漏

    1. 静态集合持有线程引用。

    2. 未正确关闭资源(如线程池未调用shutdown())。

多线程编程是 Java 开发中的核心技能,掌握线程安全、同步机制和高级工具类的使用,能够显著提升系统的性能和稳定性。在实际开发中,应根据具体场景选择合适的并发模型和工具,避免过度设计。

相关推荐
神码小Z1 分钟前
Spring Cloud Gateway 微服务网关实战指南
java·spring boot·spring cloud
ST_小罗5 分钟前
【Web前端】JavaScript入门与基础(二)
开发语言·前端·javascript
EQ-雪梨蛋花汤14 分钟前
【如何做好一份技术文档?】用Javadoc与PlantUML构建高质量技术文档(API文档自动化部署)
java·api·ci·plantuml·doc
MaCa .BaKa30 分钟前
40-智慧医疗服务平台(在线接/问诊/机器学习)
java·spring boot·mysql·机器学习·maven·numpy·pandas
Uranus^31 分钟前
深入解析Spring Boot与Spring Security的集成实践
java·spring boot·spring security·认证与授权
Livan.Tang39 分钟前
C++ 设计模式
开发语言·c++·设计模式
嘟嘟可在哪里。1 小时前
Maven打包SpringBoot项目,因包含SpringBootTest单元测试和Java预览版特性导致打包失败
java·spring boot·maven
Dust | 棉花糖1 小时前
云原生+大数据
java·大数据·云原生
woho7788991 小时前
伊吖学C笔记(3、字符、分支结构)
c语言·开发语言·笔记
十一29281 小时前
C++标准库中 std::string 类提供的 insert 成员函数的不同重载版本
开发语言·c++