线程安全

线程不安全示例

public class UnsafeTicketSeller {

    // 总票数
    private int tickets = 10;

    // 售票方法
    public void sellTicket() {
        // 检查是否还有票
        if (tickets > 0) {
            // 模拟处理时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 卖票
            tickets--;
            System.out.println("Ticket sold. Tickets left: " + tickets);
        } else {
            System.out.println("No tickets available.");
        }
    }

    public static void main(String[] args) {
        UnsafeTicketSeller seller = new UnsafeTicketSeller();

        // 创建多个线程尝试卖票
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                seller.sellTicket();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                seller.sellTicket();
            }
        });

        t1.start();
        t2.start();
    }
}

sellTicket() 方法没做同步处理,两个线程可能同时检查 tickets > 0

线程不安全原因

  1. 可见性:线程处理一个数据时,会在线程内部缓存一个副本,每次操作都是修改线程自己内部的副本变量,修改后也不会回写主存。当多个线程操作时,就会导致不准确
  2. 原子性:一组代码操作一个共享变量,这组代码假设有 10行,线程A执行到第 5 行时 CPU 时间片没了,线程 A 暂停,线程 B 去执行,这是线程 B 执行完,线程 A 得到时间片,继续执行后面的 5 行,。比如简单的 i+=1 这个会被翻译成 3 个指令:读取 i、i+1、回写i
  3. 有序性:代码经过后端编译器优化后可能不是代码原本的顺序;CPU 在执行指令时,CPU 也可能自己去优化,优化后也可能和代码原本的顺序不一致

保证线程安全

也就是要处理 可见性、原子性、有序性,只要这三者处理好了,线程就安全了。有可能某个场景线程不安全的原因只是其中之一,比如上面的卖票例子,就是可见性问题导致的

synchronized 可以解决:可见性、原子性、有序性。上面的卖票示例, sellTicket() 方法使用 synchronized 修饰,可以保证线程安全

volatile 可以解决:可见性、有序性。上面的卖票示例,tickets 变量使用 volatile 修饰,可以保证线程安全

JUC 的锁机制也能保证线程安全,JUC 提供了一系列工具类给我们使用,原子类的变量、各种锁、线程安全的集合等,底层是 AQS 实现的

posted @   CyrusHuang  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示