# 轻松摘要 - 线程安全问题

前言

       多线程在访问共享资源时 引发数据不一致或程序异常的现象。称之为 线程安全问题


                                                            正文



常见线程安全问题场景

  1. 竞态条件
    多线程争抢同一资源时,操作结果依赖于执行顺序,导致逻辑错误。

    • 场景:两线程同时对 x 进行++操作,可能导致结果少加或多加。

  2. 可见性问题
    一个线程对共享变量修改,其他线程未察觉。

    • 原因CPU 缓存编译器优化 可能导致数据不同步。
      怎么导致的, 人话?

      • CPU 缓存 导致数据不同步
                线程运行时会将变量存到 CPU缓存 中,而非直接操作主内存。如果一个线程修改了变量,但未及时刷新到主内存,其他线程读取到的仍是旧值。

      • 编译器优化 导致数据不同步
                编译器 和 CPU 可能为了性能 从而调整指令顺序,导致线程观察到的变量状态 (值) 不一致。

      场景

      ​ 语句 a = 1; flag = true; 中,另一个线程可能先观察到 flag = true,却未看到 a = 1 的更新。

      a = 1;
      flag = true; // 可能被提前执行
      if (flag) { System.out.println(a); } // 输出可能是 0
      


应对常用手段

  1. synchronized - 关键字

    • 为代码加锁,确保同一时间仅一个线程执行。

    • 场景

      public synchronized void method() {
          count++;
      }
      
  2. volatile - 关键字

    • 保证共享变量的 可见性,并阻止指令重排序。

    • 场景

      private volatile boolean flag = false;
      
  3. 显式锁(Lock)

    • 通过 java.util.concurrent.locks.ReentrantLock 提供更灵活的锁机制。

    • 基本使用:

    • lock():获取锁。如果锁已被占用,当前线程会阻塞。

    • unlock():释放锁。必须在 try-finally 块中调用,以确保锁最终会被释放。

    • 适合需要手动管理锁的场景,例如频繁获取和释放锁的复杂业务。

    示例

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockExample {
        private int count = 0;
        private final Lock lock = new ReentrantLock();
    
        public void increment() {
            lock.lock(); // 获取锁
            try {
                count++; // 线程安全的计数操作
            } finally {
                lock.unlock(); // 确保最终释放锁
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    • 优点:
      • 灵活性:
                可以手动控制锁的获取与释放,支持公平锁与非公平锁。
      • 额外功能
                支持条件变量Condition,可以实现更高级的线程协调。
    • 注意事项:
      • 必须确保 unlock() 被调用,即使代码中发生异常,否则会导致死锁。
      • 使用不当可能增加代码复杂性,不推荐在简单场景中使用。
  4. 线程安全类

    • 借助 java.util.concurrent 包中的线程安全工具类,如 ConcurrentHashMapCopyOnWriteArrayList

    • 示例

    // 使用 ConcurrentHashMap
    ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    map.put("key1", 1);
    map.computeIfPresent("key1", (key, value) -> value + 1); // 原子操作更新值
    
    // 使用 CopyOnWriteArrayList
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    list.add("item1");
    list.forEach(System.out::println); // 安全遍历
    
    
  5. 原子类

    • 使用 java.util.concurrent.atomic 提供的原子操作类,避免复杂的同步逻辑。

    • 常用方法

    • incrementAndGet():将当前值加1,并返回加后的值。

    • compareAndSet(expect, update):如果当前值等于预期值 expect,则将其更新为 update

    示例

    // 使用 AtomicInteger 进行计数
    AtomicInteger count = new AtomicInteger(0);
    
    // 自增操作
    int newValue = count.incrementAndGet(); // 原子操作,线程安全
    System.out.println("计数值:" + newValue);
    
    // Compare-and-Set 示例
    boolean updated = count.compareAndSet(1, 5); // 如果当前值是 1,则更新为 5
    System.out.println("是否更新成功:" + updated);
    

    优点:

    • 无需显式加锁,避免锁竞争导致的性能下降。
    • 简化代码逻辑,适合计数、状态标记等高频操作场景。


总结 - 核心原则

  1. 减少共享可变状态:优先使用不可变对象。
  2. 使用适当同步机制:根据需求选择 synchronizedLockvolatile
  3. 使用并发工具类:利用 java.util.concurrent 提供的线程安全库。

总结
        线程安全问题的根源在于多线程对共享资源的非同步访问。注重合理的同步机制,才能有效保证数据一致性和程序的稳定运行。最后,感谢好学的你能浏览至此,聪明的你定能早日成为行业巨匠!

end…

如果这篇文章帮到你, 帮忙点个关注呗, 不想那那那点赞或收藏也行鸭 (。•̀ᴗ-)✧ ~
评论区欢迎讨论!

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
                                                                                                                                   '(இ﹏இ`。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值