首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深入解析 Java synchronized 关键字:死锁与内存可见性

     編輯:Java 相關知識

synchronized 基础语义回顾

synchronized 是 Java 的关键字,用于修饰方法或代码块,保证同一时刻最多只有一个线程执行该段代码。其核心规则如下:

  1. 两个并发线程访问同一对象 object 中的 synchronized(this) 同步代码块时,同一时刻仅有一个线程能执行,另一个必须等待。
  2. 线程访问 synchronized(this) 代码块时,其他线程仍可访问该对象中的非同步代码块。
  3. 线程持有 synchronized(this) 锁时,其他线程对该对象所有 synchronized(this) 代码块的访问将被阻塞。
  4. 本质上,线程进入同步代码块即获取了该对象的对象锁(监视器),导致其他线程对该对象所有同步部分的访问被暂时阻塞。
  5. 上述规则对所有对象锁均适用。

简单说,synchronized 为线程声明一把锁,持有锁的线程可执行同步块内指令,其他线程需等待获取锁。

经典死锁案例:CountDownLatch 与隐式锁的冲突

作者遇到一种微妙的死锁场景:

synchronized void a() {
    countDownLatch = new CountDownLatch(1);
    // do something
    countDownLatch.await();
}

synchronized void b() {
    countDownLatch.countDown();
}

a() 由主线程调用,b() 由异步线程回调。执行结果:主线程执行 a() 后卡住,永远等待。这是典型的死锁——a 等待 b 执行,而 b 也在等待 a 释放锁。

为何 b 会等待 a?因为 synchronized 修饰实例方法时,隐式锁对象是 this(当前实例)。主线程进入 a() 时已持有 this 锁,随后调用 await() 释放 CPU 但不释放锁。异步线程回调 b() 时尝试获取同一把 this 锁,因锁被占用而阻塞,导致 countDown() 永远无法执行,主线程永久等待。

隐式锁对象验证

实例方法上的 synchronized 等价于 synchronized(this) { ... }。可通过以下代码验证:

public class MultiThreadSync {
    public synchronized void m1() throws InterruptedException {
        System.out.println("m1 call");
        Thread.sleep(2000);
        System.out.println("m1 call done");
    }
    public void m2() throws InterruptedException {
        synchronized (this) {
            System.out.println("m2 call");
            Thread.sleep(2000);
            System.out.println("m2 call done");
        }
    }
    public static void main(String[] args) {
        final MultiThreadSync obj = new MultiThreadSync();
        new Thread(() -> { try { obj.m1(); } catch (Exception e) { e.printStackTrace(); } }).start();
        new Thread(() -> { try { obj.m2(); } catch (Exception e) { e.printStackTrace(); } }).start();
    }
}

输出:

m1 call
m1 call done
m2 call
m2 call done

m2 的同步块等待了 m1 执行完毕,证明实例方法 synchronized 的隐式锁就是实例本身。静态同步方法的锁则是该类的 Class 对象。

synchronized 与内存可见性

synchronized 除了互斥,还保证内存可见性:写线程释放锁前将工作内存中的共享变量刷新到主存,读线程获取锁后从主存读取最新值。

经典可见性问题示例:

public class NoVisibility {
    private static boolean ready = false;
    private static int number = 0;
    private static class ReaderThread extends Thread {
        @Override public void run() {
            while (!ready) { Thread.yield(); }
            System.out.println(number); // 可能打印 0 或无输出
        }
    }
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

由于指令重排序(编译器或 CPU),ready = true 可能先于 number = 42 执行,导致读线程看到 ready=true 但 number 仍为 0。或写线程未及时将 number 刷入主存,读线程一直读到旧值。

利用 synchronized 修复可见性

public class VisibilityFixed {
    private static boolean ready = false;
    private static int number = 0;
    private static final Object lock = new Object();
    private static class ReaderThread extends Thread {
        @Override public void run() {
            synchronized (lock) {
                while (!ready) { Thread.yield(); }
                System.out.println(number); // 必定打印 42
            }
        }
    }
    public static void main(String[] args) {
        synchronized (lock) {
            new ReaderThread().start();
            number = 42;
            ready = true;
        }
    }
}

Java 内存模型(JSR-133)对 synchronized 定义了 happens-before 规则:

  • 监视器解锁规则:对一个监视器的解锁 happens-before 于后续对同一监视器的加锁。
  • 结合传递性,同步块内的写操作对后续获得同一锁的线程可见。

其他 happens-before 规则包括:单线程规则、volatile 变量规则、Thread.start() 规则、Thread.join() 规则、构造函数规则、传递性规则。

happens-before 实战推演


// 共享变量
Object lock = new Object();
int a = 0, b = 0, c = 0;

// 线程 A
synchronized (lock) {
    a = 1; // 1
    b = 2; // 2
} // 3 解锁
c = 3;   // 4

// 线程 B
synchronized (lock) { // 5 加锁
    System.out.println(a); // 6
    System.out.println(b); // 7
    System.out.println(c); // 8
}

假设线程 A 先执行:

  • 单线程规则:1 hb 2 hb 3 hb 4;5 hb 6 hb 7 hb 8。
  • 监视器规则:3(解锁)hb 5(加锁)。
  • 传递性:1、2 hb 6、7、8 → 线程 B 打印 a=1, b=2。
  • c=3(操作 4)与操作 8 无 happens-before 关系 → 线程 B 可能打印 c=0。

结论:synchronized 仅保护同步块内的变量可见性,同步块外的写入无顺序保证。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved