Java线程安全问题的3种主流解决方案

凌晨两点半
凌晨两点半
发布时间:2026-10-15| 4人浏览过
原创

在Java并发编程中,多个线程同时访问共享变量往往导致数据不一致。本文通过实际演示非线程安全的现象,详细介绍如何使用AtomicInteger原子类、synchronized同步锁以及ThreadLocal线程本地变量这三种主流机制来保障数据一致性,并对比各方案的操作特点与适用场景。

线程安全问题演示

线程安全是指代码在多线程环境下能够正确执行,不会出现数据错误或污染。为了直观展示非线程安全的问题,我们构建一个基础场景:定义全局变量 number 初始值为0,创建两个线程,分别对 number 执行100万次自增(++)和自减(--)操作。若程序完全线程安全,最终结果应为0;否则即为存在竞争条件。

public class ThreadSafeTest {
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number++;
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number--;
            }
        });
        t2.start();

        // 等待线程 1 和线程 2 执行完
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}

上述程序的执行结果如下所示:

从输出结果可以看出,number 的最终值通常不为0。这是因为 ++ 和 -- 并非原子操作,它们包含读取、修改、写回三个步骤,多线程交替执行时会导致部分计算被覆盖,这就是典型的线程安全问题。

解决线程安全问题

针对上述问题,Java 提供了多种解决方案。主要手段包括使用线程安全类、加锁排队执行或使用线程本地变量。接下来我们将逐一介绍具体实现。

第1步:使用原子类AtomicInteger

AtomicInteger 是 Java 并发包中提供的线程安全类,它基于CAS(Compare-And-Swap)算法将基本操作的读-改-写过程变为原子性操作,无需显式加锁即可解决并发问题。

将之前的 int 类型替换为 AtomicInteger,并使用 incrementAndGet() 和 decrementAndGet() 方法:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    // 创建 AtomicInteger
    private static AtomicInteger number = new AtomicInteger(0);
    // 循环次数
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number.incrementAndGet();
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number.decrementAndGet();
            }
        });
        t2.start();

        // 等待线程执行完毕
        t1.join();
        t2.join();
        System.out.println("最终结果:" + number.get());
    }
}

运行该程序,结果如下图所示:

可以看到,最终结果准确地返回了0。这种方式性能较高,适合简单的计数或状态更新场景。

第2步:加锁排队执行

通过锁机制确保同一时刻只有一个线程能访问共享资源,从而实现排队执行。Java中主要提供两种锁:synchronized 关键字和 ReentrantLock。

2.1 同步锁 synchronized

synchronized 是 JVM 层面实现的自动加锁和释放锁机制,使用简单且不易出错。只需在需要保护的代码块前加上 synchronized (锁对象) 即可。

public class SynchronizedExample {
    private static int number = 0;
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                synchronized (SynchronizedExample.class) {
                    number++;
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                synchronized (SynchronizedExample.class) {
                    number--;
                }
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}

执行效果如下图所示:

2.2 可重入锁 ReentrantLock

ReentrantLock 是 API 级别的锁,需要程序员手动调用 lock.lock() 和 lock.unlock()。虽然代码略显繁琐,但它提供了更灵活的锁控制能力(如尝试获取锁、公平锁等)。

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static int number = 0;
    private static final int COUNT = 1_000_000;
    // 创建 ReentrantLock
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // 手动加锁
                try {
                    number++;
                } finally {
                    lock.unlock();  // 手动释放锁
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();
                try {
                    number--;
                } finally {
                    lock.unlock();
                }
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}

注意:在实际生产环境中,建议使用 try-finally 块包裹 unlock() 调用,以确保即使发生异常也能正确释放锁,防止死锁。原文代码未包含此保护,此处已优化逻辑描述,但保留原图对应位置。

执行结果如下所示:

第3步:使用线程本地变量 ThreadLocal

ThreadLocal 提供了线程局部的变量副本。每个线程都会拥有自己的变量独立副本,互不干扰,从而从根本上避免竞争。适用于上下文传递或存储线程特有状态的场景。

实现思路:每个线程操作自己内部的计数器,最后再将各自的结果汇总到全局变量中。务必在 finally 块中调用 remove() 以防止内存泄漏。

public class ThreadLocalExample {
    // 创建 ThreadLocal(设置每个线程中的初始值为 0)
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    private static int number = 0;
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    threadLocal.set(threadLocal.get() + 1);
                }
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // 清除资源
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    threadLocal.set(threadLocal.get() - 1);
                }
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // 清除资源
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终结果:" + number);
    }
}

程序执行结果如下图所示:

总结

解决Java线程安全问题主要有三种途径:

  1. 原子类:如 AtomicInteger,适用于轻量级状态更新,性能开销低。
  2. 锁机制:如 synchronized 或 ReentrantLock,适用于复杂逻辑的资源保护,确保排他性。
  3. 线程本地变量:如 ThreadLocal,适用于隔离线程内部状态,消除共享竞争。

以上就是Java线程安全问题的3种主流解决方案的详细内容,更多关于Java多线程开发技巧的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。