返回文章列表

文章

Java中的synchronized

目录
  1. 下面从基础概念、三种用法、工作机制、注意事项和典型场景五个方面,详解 Java 中 synchronized 的用法与原理。
  2. 1. 基础概念
  3. 2. 三种用法
  4. 2.1 修饰实例方法(对象锁)
  5. 2.2 修饰静态方法(类锁)
  6. 2.3 修饰代码块(灵活锁定)
  7. 3. 工作机制与 JVM 优化
  8. 4. 注意事项
  9. 5. 典型场景示例
  10. 下面列出一系列 synchronized** 实际用法的实例**,涵盖常见并发场景、设计模式和线程协作,帮助你理解 synchronized 在不同上下文中的使用。
  11. 🌟 1. 方法同步(实例锁)
  12. 🌟 2. 静态方法同步(类锁)
  13. 🌟 3. 同步代码块:细粒度控制
  14. 🌟 4. 多个锁保护不同资源(避免锁竞争)
  15. 🌟 5. 线程安全的单例模式(双重检查锁)
  16. 🌟 6. 线程协作:wait/notify 配合 synchronized
  17. 🌟 7. 死锁示例(注意避免)
  18. 🌟 8. 多线程售票系统
  19. 🌟 9. 保护共享集合
  20. 🌟 10. 嵌套 synchronized(重入锁)
  21. 📎 参考文章

下面从基础概念、三种用法、工作机制、注意事项和典型场景五个方面,详解 Java 中 synchronized 的用法与原理。#

1. 基础概念#

  • 互斥锁(Mutex)synchronized 强制同一时刻只有一个线程可以执行被它修饰的代码块或方法,以保证共享资源的线程安全。
  • 监视器(Monitor):每个 Java 对象都有一个与之关联的监视器锁(也称对象监视器)。线程要么持有该监视器锁才能进入对应的 synchronized 区域,要么被阻塞等待。

2. 三种用法#

2.1 修饰实例方法(对象锁)#

public synchronized void foo() {
    // 等同于 synchronized(this) { … }
    // 对当前实例加锁
}
  • 锁对象:this
  • 作用范围:整个方法体
  • 同一时刻,只有一个线程能执行该实例的任何一个 synchronized 实例方法。

2.2 修饰静态方法(类锁)#

public static synchronized void bar() {
    // 等同于 synchronized(MyClass.class) { … }
    // 对 Class 对象加锁
}
  • 锁对象:对应的 Class 对象
  • 作用范围:整个静态方法
  • 不同实例间共享同一把类锁;静态方法与实例方法所用锁相互独立。

2.3 修饰代码块(灵活锁定)#

public void baz() {
    synchronized(lockObject) {
        // 只对 lockObject 加锁
        // lockObject 可以是 this、Class 对象或任意非空引用
    }
}
  • 锁对象:由程序员指定
  • 作用范围:仅限于代码块内部
  • 推荐:当只需要保护局部临界区时,用代码块可以缩小锁粒度,提升并发性能。

3. 工作机制与 JVM 优化#

  1. 进入与退出
    • 进入:线程尝试获取监视器锁,若锁空闲则获得并进入;否则进入阻塞队列等待。
    • 退出:执行完方法/块或抛出异常时,自动释放监视器锁,并唤醒等待队列中下一个线程。
  2. 重入性(Reentrant)
    • 同一线程可对同一把锁重复加锁(如在一个 synchronized 方法里调用另一个同锁方法),JVM 会记录加锁计数,退出时需对应释放。
  3. 锁优化(HotSpot JVM)
    • 偏向锁:默认倾向于第一个获得锁的线程,减少后续竞争时的 CAS 操作。
    • 轻量级锁:在无或者低竞争场景下,用对象头中的栈锁记录持有者,避免操作系统的重量级阻塞。
    • 重量级锁:高竞争时升级,会进入操作系统 Mutex,阻塞与唤醒成本较高。

4. 注意事项#

  • 锁粒度
    • 过大:会严重影响并发性能。
    • 过小:可能产生更多的锁开销并增加死锁风险。
    • 建议:只在必要的临界区使用 synchronized 代码块。
  • 锁对象选择
    • 切勿使用可变对象或公共变量(如:String 常量、Integer 装箱对象等)做锁,否则可能遭遇意外锁竞争。
    • 最好使用私有的、专用的锁对象:

private final Object lock = new Object(); ```

  • 死锁风险
    • 当多个线程互相持有对方需要的锁且都在等待时,就会死锁。
    • 预防:
      1. 始终以相同顺序获取多把锁。
      2. 尽量减少嵌套锁。
      3. 设置超时或尝试锁(可考虑 ReentrantLock.tryLock())。
  • 可见性与有序性
    • synchronized 不仅保证互斥,还具备 内存屏障 功能:
      • 释放锁前,线程会把自己工作内存中的变量刷新回主内存。
      • 竞争锁成功前,会清空工作内存并从主内存重新读取最新值。
    • 因此 synchronized 块外的读写对锁内的操作可见。

5. 典型场景示例#

下面列出一系列 synchronized** 实际用法的实例**,涵盖常见并发场景、设计模式和线程协作,帮助你理解 synchronized 在不同上下文中的使用。#

🌟 1. 方法同步(实例锁)#

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

同步的是整个方法,相当于 synchronized(this)。适合方法体不复杂,锁粒度可以稍大一些的场景。


🌟 2. 静态方法同步(类锁)#

public class GlobalCounter {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }

    public static synchronized int getCount() {
        return count;
    }
}

synchronized 修饰静态方法,锁住的是 GlobalCounter.class,所有线程共享该类锁。


🌟 3. 同步代码块:细粒度控制#

public class FineGrainedCounter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

只锁需要保护的代码,减小锁范围,有利于并发性能。


🌟 4. 多个锁保护不同资源(避免锁竞争)#

public class MultiLockExample {
    private int a = 0;
    private int b = 0;

    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void modifyA() {
        synchronized (lockA) {
            a++;
        }
    }

    public void modifyB() {
        synchronized (lockB) {
            b++;
        }
    }
}

分离资源,加快并发处理速度。


🌟 5. 线程安全的单例模式(双重检查锁)#

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile 防止指令重排,synchronized 保证线程安全。


🌟 6. 线程协作:wait/notify 配合 synchronized#

public class WaitNotifyExample {
    private final Object lock = new Object();
    private boolean condition = false;

    public void doWait() throws InterruptedException {
        synchronized (lock) {
            while (!condition) {
                System.out.println("Thread waiting...");
                lock.wait();
            }
            System.out.println("Condition met, proceeding.");
        }
    }

    public void doNotify() {
        synchronized (lock) {
            condition = true;
            lock.notifyAll();
            System.out.println("Condition updated, notified.");
        }
    }
}

wait() 和 notify() 必须在 synchronized 内部使用,因为它们需要当前线程持有锁。


🌟 7. 死锁示例(注意避免)#

public class DeadlockExample {
    private final Object resourceA = new Object();
    private final Object resourceB = new Object();

    public void method1() {
        synchronized (resourceA) {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            synchronized (resourceB) {
                System.out.println("method1 acquired both locks");
            }
        }
    }

    public void method2() {
        synchronized (resourceB) {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            synchronized (resourceA) {
                System.out.println("method2 acquired both locks");
            }
        }
    }
}

两个线程互相等待对方释放锁会造成死锁。避免方法:统一加锁顺序 或使用 tryLock。


🌟 8. 多线程售票系统#

public class TicketSeller implements Runnable {
    private int tickets = 10;

    public void run() {
        while (true) {
            synchronized (this) {
                if (tickets <= 0) break;
                System.out.println(Thread.currentThread().getName() + " sold ticket: " + tickets--);
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            }
        }
    }

    public static void main(String[] args) {
        TicketSeller seller = new TicketSeller();
        new Thread(seller, "Thread A").start();
        new Thread(seller, "Thread B").start();
    }
}

演示共享资源竞争和线程同步。


🌟 9. 保护共享集合#

import java.util.*;

public class SafeList<T> {
    private final List<T> list = new ArrayList<>();

    public void add(T element) {
        synchronized (list) {
            list.add(element);
        }
    }

    public T get(int index) {
        synchronized (list) {
            return list.get(index);
        }
    }
}

使用 Collections.synchronizedList(...) 可以避免手动同步。


🌟 10. 嵌套 synchronized(重入锁)#

public class ReentrantExample {
    public synchronized void outer() {
        System.out.println("Outer");
        inner();
    }

    public synchronized void inner() {
        System.out.println("Inner");
    }
}

Java 的内置锁是可重入的,即一个线程可以重复获取同一个锁而不会死锁。


通过以上内容,你应当能系统地掌握 synchronized 在 Java 中的各种用法、背后机制与注意事项,并能在实际并发场景中灵活运用。

📎 参考文章#