线程同步

线程同步与通信

线程同步

线程同步是指多个线程在访问共享资源时,通过某种机制确保同一时间只有一个线程可以访问该资源,从而避免数据不一致或竞态条件(Race Condition)的发生

  1. synchronized 关键字

    • synchronized 是Java中最常用的同步机制。它可以用于方法或代码块,确保同一时间只有一个线程可以执行被synchronized修饰的代码

    • 当一个线程进入synchronized方法时,它会获取该方法所属对象的锁,其他线程必须等待锁释放后才能进入

      public synchronized void method() {
          // 同步方法
          // 实例方法的锁对象为当前实例对象(this)
          // 静态方法的锁对象为当前类的 Class 对象
      }
      
      public void method() {
          synchronized (this) {
              // 同步代码块
              //同步代码块允许更细粒度的控制,可以指定锁对象(如this、Object等)
          }
      }
      
  2. ReentrantLock

    • ReentrantLock是java.util.concurrent.locks包中的一个类,提供了比synchronized更灵活的锁机制

    • ReentrantLock允许更复杂的锁操作,如尝试获取锁、超时获取锁、可中断获取锁等

      ReentrantLock lock = new ReentrantLock();
      
      public void method() {
          lock.lock();  // 获取锁
          try {
              // 线程安全的代码
          } finally {
              lock.unlock();  // 释放锁
          }
      }
      
  3. volatile 关键字

    • volatile用于修饰变量,确保变量的可见性。当一个线程修改了volatile变量的值,其他线程可以立即看到修改后的值

    • 当变量被声明为 volatile 时,任何线程修改该变量的值都会 立即刷新到主内存,其他线程读取该变量时会 直接从主内存获取最新值,而非使用本地缓存(如 CPU 缓存)

      volatile boolean flag = false;
      // 线程 A 
      flag = true; // 修改后其他线程立即可见
      // 线程 B 
      while (!flag) { /* 循环直到 flag 变为 true */ }
      
      //若不使用 `volatile`,线程 B 可能因缓存不一致陷入死循环
      
    • volatile不保证原子性,因此它通常用于简单的标志位或状态变量

    禁止指令重排序

    • JVM 和 CPU 可能对代码执行顺序进行优化(指令重排序),但 volatile 通过插入 内存屏障(Memory Barrier)阻止这种优化,确保代码执行顺序符合预期

    • 在单例模式的双重检查锁定(Double-Checked Locking)中,若实例变量不用 volatile 修饰,可能导致其他线程获取未初始化完全的对象

      public class Singleton {
          private static Singleton instance;  // 未使用 volatile 
          
          public static Singleton getInstance() {
              if (instance == null) {  // 第一次检查(无锁)
                  synchronized (Singleton.class)  {
                      if (instance == null) {  // 第二次检查(加锁后)
                          instance = new Singleton();  // 问题根源在此 
                      }
                  }
              }
              return instance;
          }
      }
      /*
      Java 对象的创建过程可分为三步:
      	1. 分配内存空间(为对象分配堆内存);
      	2. 初始化对象(调用构造函数、设置字段初始值);
      	3. 将引用指向内存地址(赋值给变量 instance)。
          
      JVM 允许对步骤 2 和步骤 3 进行指令重排序。若发生重排序,其他线程可能在对象未完成初始化时,看到 instance 已非空,从而直接使用一个部分初始化的对象1。
      
      2. 内存可见性问题
      即使未发生指令重排序,若 instance 未用 volatile 修饰,线程可能从本地缓存中读取过期的 instance 值(例如线程 A 完成初始化后,线程 B 仍认为 instance 为 null),导致重复创建对象
      */
      

synchronized锁对象的释放

  • 同步方法或者代码块执行结束
  • 同步方法或者代码块执行出现未处理的错误,导致异常结束该线程
  • 调用锁对象的wait()方法
posted @ 2025-03-17 21:09  QAQ001  阅读(18)  评论(0)    收藏  举报