文档

Java™ 教程-Java Tutorials 中文版
内部锁和同步
Trail: Essential Classes
Lesson: Concurrency
Section: Synchronization

内部锁和同步

同步是围绕称为 intrinsic lock (内部锁)monitor lock (监视器锁) 的内部实体构建的。(API 规范通常将此实体简称为“监视器”。)内部锁在同步的两个方面都发挥作用:强制对对象状态进行独占访问,并建立对可见性至关重要的 happens-before 关系。

每个对象都有一个与之相关联的内部锁。按照规范,需要对对象字段进行独占和一致访问的线程必须在访问对象之前 acquire (获取) 对象的内部锁,然后在完成时 release (释放) 内部锁。一个线程在获取锁和释放锁之间的时间段内,被称为 own (拥有) 内部锁。只要一个线程拥有一个内部锁,其他线程就不可以获取相同的锁。另一个线程在尝试获取锁时将阻塞。

当线程释放内部锁时,在该操作与同一锁的任何后续获取之间建立 happens-before 关系。

同步方法中的锁

当线程调用同步方法时,它会自动获取该方法的对象的内部锁,并在方法返回时释放它。即使返回是由未捕获的异常引起的,也会释放锁。

你可能想知道在调用静态同步方法时会发生什么,因为静态方法与类关联,而不是与对象关联。在这种情况下,线程获取与该类关联的 Class 对象的内部锁。因此,访问类的静态字段是由一个锁控制的,该锁与任何该类的实例的锁都不同。

同步语句

创建同步代码的另一种方法是使用 synchronized statements (同步语句)。与同步方法不同,同步语句必须指定提供内部锁的对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在此示例中,addName 方法需要将更改同步到 lastNamenameCount,但还需要避免其他对象方法的同步调用。(从同步代码调用其他对象的方法可能会产生 Liveness 一节中描述的问题。)如果没有同步语句,则必须有一个单独的,不同步的方法,仅用于调用 nameList.add

同步语句对于通过细粒度的同步来提高并发性也很有用。例如,假设 MsLunch 类有两个实例字段,c1c2,它们从不一起使用。必须同步这些字段的所有更新,但是没有理由阻止 c1 的更新与 c2 的更新交错 — 这样做会创建不必要的阻塞,从而降低并发性。我们创建了两个对象单独提供锁,而不是使用同步方法或使用与 this 关联的锁。

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

谨慎使用这种惯用方式。你必须绝对确保对受影响字段的访问进行交错是安全的。

Reentrant Synchronization (重入同步)

回想一下,线程无法获取另一个线程拥有的锁。但是线程 can (可以) 获取它已经拥有的锁。允许线程多次获取相同的锁启用了 reentrant synchronization (重入同步)。这描述了一种情况,其中同步代码直接或间接地调用也包含同步代码的方法,并且两组代码使用相同的锁。在没有重入同步的情况下,同步代码必须采取许多额外的预防措施,以避免线程导致自身阻塞。


Previous page: Synchronized Methods
Next page: Atomic Access