解决Java中线程安全问题

 更新时间:2026年01月12日 08:50:58   作者:我会替风去  
线程安全问题源于多个线程并发访问共享可变资源,破坏了原子性、可见性或有序性,解决方法包括避免共享可变资源、使用同步/加锁机制以及volatile关键字,本文给大家介绍解决Java中线程安全问题,感兴趣的朋友跟随小编一起看看吧

线程安全问题的核心原因

  • 线程安全问题本质是多个线程并发访问共享且可变的资源时,操作的原子性、可见性或有序性被破坏,导致程序执行结果不符合预期。
  • 根本原因:共享可变资源
  • 共享资源:多个线程都能访问到的资源(如成员变量、静态变量、共享内存区域);
  • 可变资源:资源的状态(值)可以被修改(如int计数器、HashMap的元素);
  • 经典的i++ 操作。它在底层分为“读取-修改-写入”三个步骤。如果两个线程同时读取 i=1,各自加1后写回,结果是2而不是3。
  • 直接原因:三大特性被破坏
  • Java内存模型(JMM)定义的多线程并发三大核心特性,任何一个被破坏都会引发线程安全问题:
    • 原子性:一个操作(如count++)包含“读 - 改 - 写”三步,非原子操作会被多线程交错执行;
    • 可见性:线程修改共享变量后,不会立即同步到主内存,其他线程读取的仍是旧值;
    • 有序性:JVM的指令重排序优化,会导致多线程下执行顺序混乱(如未加volatile的双重检查锁单例)。

线程安全问题的解决方案

  • 核心思路:要么避免共享可变资源(从根源消除问题),要么控制并发访问规则(保证三大特性)。

方案1:避免共享可变资源(优先推荐)

  • 栈封闭(局部变量):局部变量存储在线程私有栈中,每个线程有独立副本,天然线程安全。
public class StackClosedDemo {
    // 每个线程调用该方法时,都会创建独立的count副本
    public void calculate() {
        int count = 0;
        count++; // 无线程安全问题
        System.out.println(Thread.currentThread().getName() + ": " + count);
    }
    public static void main(String[] args) {
        StackClosedDemo demo = new StackClosedDemo();
        // 10个线程各自操作自己的局部变量
        for (int i = 0; i < 10; i++) {
            new Thread(demo::calculate, "Thread-" + i).start();
        }
    }
}
  • 不可变对象:对象创建后状态不可修改(如String、Integer),即使共享也无法修改值。
// 自定义不可变类(final类+final成员变量+无setter)
public final class ImmutableUser {
    private final String name;
    private final int age;
    public ImmutableUser(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 仅提供getter,无setter
    public String getName() { return name; }
    public int getAge() { return age; }
}
  • ThreadLocal(线程本地存储):为每个线程提供独立的变量副本,线程操作自身副本,互不干扰。
public class ThreadLocalDemo {
    // 每个线程有独立的Integer副本,初始值为0
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    public void increment() {
        threadLocal.set(threadLocal.get() + 1);
        System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
    }
    public static void main(String[] args) {
        ThreadLocalDemo demo = new ThreadLocalDemo();
        // 3个线程各自操作自己的副本
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 2; j++) {
                    demo.increment();
                }
            }, "Thread-" + i).start();
        }
    }
}
// 输出(顺序可能不同):
// Thread-0: 1、Thread-0: 2
// Thread-1: 1、Thread-1: 2
// Thread-2: 1、Thread-2: 2

方案2:同步/加锁(控制并发访问)

互斥同步(阻塞同步):这是最常见的方案,通过加锁来保证同一时刻只有一个线程操作资源。

  • synchronized 关键字:Java 原生支持,使用简单。可修饰方法或代码块。属于不可中断的锁。
public class SynchronizedDemo {
    private int count = 0;
    // 同步实例方法,锁是this对象
    public synchronized void increment() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo demo = new SynchronizedDemo();
        // 1000个线程执行increment
        for (int i = 0; i < 1000; i++) {
            new Thread(demo::increment).start();
        }
        Thread.sleep(1000);
        System.out.println("最终count:" + demo.count); // 输出1000
    }
}

ReentrantLock(显式锁):比synchronized灵活(可中断、可超时、公平锁),需手动释放锁(必须在finally中)。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
    private int count = 0;
    private Lock lock = new ReentrantLock(); // 默认非公平锁
    public void increment() {
        lock.lock(); // 加锁
        try {
            count++;
        } finally {
            lock.unlock(); // 释放锁,避免死锁
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo demo = new ReentrantLockDemo();
        for (int i = 0; i < 1000; i++) {
            new Thread(demo::increment).start();
        }
        Thread.sleep(1000);
        System.out.println("最终count:" + demo.count); // 输出1000
    }
}

方案3:volatile关键字(保证可见性/有序性)

  • 保证可见性:强制失效工作内存,直接读写主内存。
  • 保证有序性:禁止指令重排序。
  • 注意:它不保证原子性(不能解决i++问题)。
public class VolatileDemo {
    private volatile boolean stop = false; // 保证可见性和有序性
    public void runThread() {
        new Thread(() -> {
            int i = 0;
            while (!stop) { // 能立即感知stop的修改
                i++;
            }
            System.out.println("线程停止,i=" + i);
        }).start();
    }
    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();
        demo.runThread();
        Thread.sleep(3000);
        demo.stop = true; // 修改后,线程立即停止
    }
}

到此这篇关于Java中线程安全问题的原因和解决方案的文章就介绍到这了,更多相关java线程安全内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java终止正在运行的线程的三种方法

    Java终止正在运行的线程的三种方法

    停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了Java终止正在运行的线程的三种方法,需要的朋友可以参考下
    2025-03-03
  • idea中使用git插件回滚代码的流程步骤

    idea中使用git插件回滚代码的流程步骤

    使用idea开发java代码时,如果想回滚git提交的代码, 需要操作三步,本篇步骤操作前,前提是你的电脑已经安装了git插件,并且你的idea也集成了git插件,下面是详细步骤,需要的朋友可以参考下
    2025-04-04
  • 详解Spring mvc的web.xml配置说明

    详解Spring mvc的web.xml配置说明

    本篇文章主要介绍了Spring mvc的web.xml配置说明,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • 使用filter实现url级别内存缓存示例

    使用filter实现url级别内存缓存示例

    这篇文章主要介绍了使用filter实现url级别内存缓存示例,只需要一个静态类,在filter中调用,也可以全部写到filt里面。可以根据查询参数分别缓存,需要的朋友可以参考下
    2014-03-03
  • 关于BufferedReader读取文件指定字符集问题

    关于BufferedReader读取文件指定字符集问题

    这篇文章主要介绍了关于BufferedReader读取文件指定字符集问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • spring boot集成WebSocket日志实时输出到web页面

    spring boot集成WebSocket日志实时输出到web页面

    这篇文章主要为大家介绍了spring boot集成WebSocket日志实时输出到web页面展示的详细操作,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • MyBatisPlus批量添加的优化与报错解决

    MyBatisPlus批量添加的优化与报错解决

    MybatisPlus是一个高效的java持久层框架,它在Mybatis的基础上增加了一些便捷的功能,提供了更加易用的API,可以大幅度提高开发效率,这篇文章主要给大家介绍了关于MyBatisPlus批量添加的优化与报错解决的相关资料,需要的朋友可以参考下
    2023-05-05
  • 在Ubuntu系统下安装JDK和Tomcat的教程

    在Ubuntu系统下安装JDK和Tomcat的教程

    这篇文章主要介绍了在Ubuntu系统下安装JDK和Tomcat的教程,这样便是在Linux系统下搭建完整的Java和JSP开发环境,需要的朋友可以参考下
    2015-08-08
  • Java中的gateway自定义过滤器详解

    Java中的gateway自定义过滤器详解

    这篇文章主要介绍了Java中的gateway自定义过滤器详解,过滤器是指gateway在路由过程中(A地址路由到B地址)生效进行过滤操作的,所有首先你得先配一个地址路由,本文提供了部分实现代码,需要的朋友可以参考下
    2023-11-11
  • MyBatis Plus逻辑删除和分页插件使用详解

    MyBatis Plus逻辑删除和分页插件使用详解

    这篇文章主要介绍了MyBatis Plus之逻辑删除和分页插件使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12

最新评论