java学习--什么是线程安全和不安全

一、核心概念(通俗解释)

先记住一个核心场景:多个线程同时操作同一个共享数据

  • 线程安全:不管多个线程怎么同时操作,最终的结果都是 "正确的"、"符合预期的",数据不会错乱。
  • 线程不安全:多个线程同时操作时,会出现数据错乱、结果不符合预期的情况(比如计数不准、数据丢失 / 重复)。

可以类比成:

  • 线程不安全:多人同时抢着修改同一份 Excel 表格,最后表格数据乱了;
  • 线程安全:给 Excel 加了锁,同一时间只有一个人能改,改完其他人才能改,数据始终是对的。

二、代码演示(直观看到线程不安全的问题)

下面用 "多线程计数" 的例子,对比线程不安全(ArrayList)和线程安全(Vector/CopyOnWriteArrayList)的差异:

1. 线程不安全的示例(ArrayList)
复制代码
import java.util.ArrayList;
import java.util.List;

public class ThreadUnsafeDemo {
    // 共享数据:多个线程都会操作这个list
    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建100个线程,每个线程往list里加100个元素
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    list.add(j); // 多个线程同时执行add,线程不安全
                }
            }).start();
        }

        // 等待所有线程执行完
        Thread.sleep(2000);
        // 预期结果:100线程 × 100元素 = 10000个元素
        System.out.println("ArrayList最终元素个数:" + list.size());
    }
}

运行结果 :大概率不是 10000(比如 9876、9950 等),甚至可能抛出 ArrayIndexOutOfBoundsException(数组索引越界)。原因ArrayListadd 方法没有加锁,多个线程同时执行时,会出现 "数据覆盖" 或 "扩容时的数组操作冲突",导致元素丢失或索引错乱。

2. 线程安全的示例(Vector)

只需要把上面的 ArrayList 换成 Vector

复制代码
import java.util.List;
import java.util.Vector;

public class ThreadSafeDemo {
    // 换成线程安全的Vector
    private static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    list.add(j); // Vector的add方法加了synchronized,线程安全
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("Vector最终元素个数:" + list.size());
    }
}

运行结果 :几乎每次都是 10000(符合预期)。原因Vectoradd 方法加了 synchronized 关键字,相当于给方法加了 "锁",同一时间只有一个线程能执行 add,避免了数据冲突。

三、深入理解:为什么会线程不安全?

线程不安全的本质是非原子操作 + 没有同步控制 。以 ArrayList.add() 为例,它的核心逻辑分 3 步(非原子操作,不能一次性完成):

  1. 检查数组容量,不够则扩容;
  2. 把元素放到数组的指定位置(elementData[elementCount] = e);
  3. 元素计数 + 1(elementCount++)。

如果两个线程同时执行这 3 步,就会出现:

  • 线程 A 执行完步骤 2,还没执行步骤 3;
  • 线程 B 此时也执行步骤 2,把线程 A 的元素覆盖了;
  • 最终两个线程只加了 1 个元素,导致总数少了。

而线程安全的实现,就是给这些 "非原子操作" 加同步锁 (如 synchronized),让这些步骤变成 "原子性" 的 ------ 要么全执行完,要么不执行,中间不会被其他线程打断。

四、线程安全 vs 线程不安全 核心对比

特性 线程安全 线程不安全
数据一致性 多线程操作后数据符合预期 多线程操作后数据错乱 / 丢失
实现方式 加锁(synchronized/Lock)、原子类等 无任何同步控制
性能 低(锁的开销) 高(无锁,直接执行)
典型例子 Vector、CopyOnWriteArrayList、Hashtable ArrayList、HashMap、HashSet
适用场景 多线程同时读写共享数据 单线程、仅读操作、无并发修改

五、补充:不是所有场景都需要线程安全

线程安全是有 "代价" 的(性能损耗),所以要根据场景选择:

  1. 单线程场景 :用线程不安全的类(如 ArrayList),性能更高;
  2. 多线程仅读 :即使是 ArrayList,只读也不会有问题(因为没有数据修改);
  3. 多线程有写操作 :必须用线程安全的类,或自己加锁(如 synchronized 代码块)。

总结

  1. 线程安全:多线程同时操作共享数据时,通过同步机制(如锁)保证数据结果正确、无错乱;
  2. 线程不安全:多线程同时操作共享数据时,无同步控制,会出现数据覆盖、丢失、索引异常等问题;
  3. 线程安全以牺牲性能为代价,选择时需结合场景:单线程 / 仅读用不安全类,多线程写用安全类。
相关推荐
爱睡懒觉的焦糖玛奇朵19 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具使用说明】
人工智能·python·深度学习·学习·算法·yolo·音视频
夏天想20 小时前
人类将从“执行者“变为“总导演”,学习Ai知识
人工智能·学习
晓梦林21 小时前
Baji1靶场学习笔记
笔记·学习
Java面试题总结21 小时前
java高频面试题(2026最新)
java·开发语言·jvm·数据库·spring·缓存
网教盟人才服务平台21 小时前
全国政务网络安全能力提升行动启动,筑牢政务数据安全防线
安全·web安全·政务
希冀12321 小时前
【CSS学习第十一篇】
前端·css·学习
黎阳之光21 小时前
黎阳之光:以视频孪生重构智能监盘,为燃机打造新一代智慧电厂大脑
大数据·人工智能·算法·安全·数字孪生
汽车仪器仪表相关领域21 小时前
Kvaser Hybrid Pro 2xCAN/LIN 双通道可编程CAN/LIN通讯接口:一机双模可编程,汽车车身混合总线测试专用设备
人工智能·功能测试·安全·fpga开发·汽车·压力测试
苦逼的猿宝21 小时前
学生心理咨询评估系统
java·毕业设计·springboot·计算机毕业设计
隔窗听雨眠21 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端