Java并发编程:synchronized锁的粒度与死锁的根源

171 阅读2分钟

一、动态锁(对象锁):线程间的互斥

动态锁,也称为对象锁,是 synchronized 关键字修饰非静态方法同步代码块时产生的锁。

  • 锁定对象:动态锁锁住的是当前对象实例(this
  • 互斥范围同一时刻,只有一个线程可以访问同一个对象实例的同步方法或同步代码块。但不同线程可以同时访问不同对象实例的同步方法。
public class DynamicLockDemo {
    // 动态锁方法,锁住this对象
    public synchronized void instanceMethod() {
        System.out.println(Thread.currentThread().getName() + "进入实例方法");
        // ...
    }
    
    public void anotherMethod() {
        // 动态锁代码块,锁住this对象
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + "进入同步代码块");
            // ...
        }
    }
}

二、静态锁(类锁):全局的互斥

静态锁,也称为类锁,是 synchronized 关键字修饰静态方法同步代码块(以 ClassName.class 为参数)时产生的锁。

  • 锁定对象:静态锁锁住的是该类的 Class 对象
  • 互斥范围同一时刻,只有一个线程可以访问该类的静态同步方法或同步代码块。因为每个类在 JVM 中只有一个 Class 对象,所以静态锁是全局唯一的。
public class StaticLockDemo {
    // 静态锁方法,锁住StaticLockDemo.class
    public static synchronized void staticMethod() {
        System.out.println(Thread.currentThread().getName() + "进入静态方法");
        // ...
    }
    
    public void instanceMethod() {
        // 静态锁代码块,锁住Class对象
        synchronized (StaticLockDemo.class) {
            System.out.println(Thread.currentThread().getName() + "进入同步代码块");
            // ...
        }
    }
}

三、死锁:多线程并发的致命陷阱

死锁是多线程并发中的一种僵局。当两个或多个线程各自持有一个锁,并试图获取对方持有的锁时,就会发生死锁。

  • 四大必要条件

    1. 互斥(Mutual Exclusion) :一个锁一次只能被一个线程持有。
    2. 请求和保持(Hold and Wait) :线程已经持有一个锁,同时又在请求另一个锁。
    3. 不可抢占(No Preemption) :锁只能由持有它的线程释放,不能被其他线程强制剥夺。
    4. 循环等待(Circular Wait) :多个线程形成一个环形等待链。
  • 避免死锁的策略

    1. 固定锁顺序:所有线程都按照相同的顺序获取锁。
    2. 超时获取锁:使用 ReentrantLocktryLock() 方法,在指定时间内无法获取锁时,自动放弃。
    3. 减少锁粒度:只在需要同步的代码块上加锁,以减小锁的范围。