【翻译十】java-固定锁和同步

时间:2024-01-19 13:30:14

Intrinsic Locks and Synchronization

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.") Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.

Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock.

When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquistion of the same lock.

Locks In Synchronized Methods

When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.

You might wonder what happens when a static synchronized method is invoked, since a static method is associated with a class, not an object. In this case, the thread acquires the intrinsic lock for the Class object associated with the class. Thus access to class's static fields is controlled by a lock that's distinct from the lock for any instance of the class.

Synchronized Statements

Another way to create synchronized code is with synchronized statements. Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock:

public void addName(String name) {

synchronized(this) {

lastName = name;

nameCount++;

}

nameList.add(name);

}

In this example, the addName method needs to synchronize changes to lastName and nameCount, but also needs to avoid synchronizing invocations of other objects' methods. (Invoking other objects' methods from synchronized code can create problems that are described in the section on Liveness.) Without synchronized statements, there would have to be a separate, unsynchronized method for the sole purpose of invoking nameList.add.

Synchronized statements are also useful for improving concurrency with fine-grained synchronization. Suppose, for example, class MsLunch has two instance fields, c1 and c2, that are never used together. All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

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++;

}

}

}

Use this idiom with extreme care. You must be absolutely sure that it really is safe to interleave access of the affected fields.

Reentrant Synchronization

Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.

译文:

固定锁和同步机制

  同步机制是建立在被称为固定锁或者监控锁的内部实体之上的。(API 说明书通常指这种实体为”monitor”)固定锁在线程同步机制中扮演着两个角色:强制拥有一个对象进入的状态和创建确保基本可见的事先的关系。

  每一个对象都有一个固定锁关联它。按照惯例,一个线程需要占用和进入一个对象域必须在进入之前事先捕获到这个对象的内部锁,让后当执行完就释放这个锁。一个线程拥有这个固定锁是指在他捕获到这个锁到释放这个固定锁的时间之间。在这个线程拥有这个固定锁的时候,没有其他的线程能够捕获到这个固定锁。

同步方法中的锁

  当一个线程执行一个同步方法,它自动为这个方法的对象捕获到固定锁当返回时并释放它。即使这个返回是被一个未捕获到的异常引起的也会释放这个固定锁。

你可能会想到当一个静态的Synchronized方法被执行的情况,这是由于一个静态方法是关联到一个类的,而不是一个对象。在这种情况下,线程捕获到类对象关联到类的固定锁。因此静态类的访问控制是有别于任何其他类的实例的锁的访问控制的。

同步语句

  另外一种创建同步方法的代码是同步语句。不像同步方法,同步语句必须指定提供固定锁的对象。

 public void addName(String name) {

     synchronized(this) {

         lastName = name;

         nameCount++;

     }

     nameList.add(name);

 }

  在这个实例中,andName方法需要同步lastName和nameCount的改变,但是也需要避免同步请求其他对象的方法。(在同步的语句中执行其他对象的方法可能引起将会在Liveness中描述的问题。)除了同步语句,这里将会有一种分割,非同步的方法一唯一的目的调用nameList.add。

同步语句对细粒度的同步提高并发也是有用的。假如,MsLunch类有两个实例,c1和c2,并且它们不会在一起使用。所有对这些域的改变都应该是同步的,但是没有理由阻止c1和c2进行交叉的更新。而且这样做可以减少并发造成不必要的阻塞。我们创建了两个对象提供锁,而不是利用同步方法或其他关联到此的锁机制。

 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++;
} } }

  使用这个约定俗称的方法要格外小心。你必须肯定交叉访问的区域是绝对安全的。

重复进入同步

  回想起一个线程不能获得另外一个线程占用的锁。但是一个线程能够获得它已经拥有的锁。允许一个线程获得相同的锁超过一次使重复进入线程成为可能。这描述了一种情况同步代码直接或者非直接执行一个也包含Synchronized的代码,并且两个代码集合用相同的锁。除了重复进入同步语句,同步代码必须采取许多措施预防它自己使它自己阻塞。

养眼是必须滴^^

【翻译十】java-固定锁和同步