内在锁和同步
同步是围绕称为内部锁或监视器锁的内部实体构建的。 (API 规范通常将此实体简称为“监视器”.)内在锁在同步的两个方面都起作用:强制对对象状态的独占访问并构建对可见性至关重要的事前关联。
每个对象都有一个与之关联的固有锁。按照约定,需要对对象的字段进行独占且一致的访问的线程必须在访问对象之前先获取对象的固有锁,然后在完成对它们的锁定后释放固有锁。据说线程在获取锁和释放锁之间拥有内部锁。只要一个线程拥有一个内在锁,其他任何线程都无法获得相同的锁。另一个线程在try获取锁时将阻塞。
当线程释放固有锁时,该动作与任何随后的相同锁获取之间将构建事前发生的关系。
同步方法中的锁
当线程调用同步方法时,它将自动获取该方法对象的内在锁,并在方法返回时释放该内在锁。即使返回是由未catch的异常引起的,也会发生锁定释放。
您可能想知道当调用静态同步方法时会发生什么,因为静态方法与类而不是对象相关联。在这种情况下,线程获取与该类关联的Class
对象的固有锁定。因此,通过与该类的任何实例的锁不同的锁来控制对类的静态字段的访问。
Synchronized 语句
创建同步代码的另一种方法是使用同步语句。与同步方法不同,同步语句必须指定提供内部锁的对象:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
在此示例中,addName
方法需要将对lastName
和nameCount
的更改同步,但还需要避免对其他对象的方法的调用进行同步。 (从同步代码中调用其他对象的方法可能会产生在Liveness一节中描述的问题。)如果没有同步语句,则仅出于调用nameList.add
的 Object 就必须有一个单独的非同步方法。
同步语句对于通过细粒度同步提高并发性也很有用。例如,假设类MsLunch
具有两个实例字段c1
和c2
,它们从未一起使用。这些字段的所有更新都必须同步,但是没有理由阻止 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
回想一下,一个线程无法获取另一个线程拥有的锁。但是线程可以获取它已经拥有的锁。允许一个线程多次获取相同的锁可启用重入同步。这描述了一种情况,其中同步代码直接或间接调用也包含同步代码的方法,并且两组代码使用相同的锁。如果没有可重入同步,则同步代码将不得不采取许多其他预防措施,以避免线程自身阻塞。