滥用同步组件是造成并发问题的一个普遍性根源,在一个可能被重用的对象上加锁可能造成死锁或者其他不确定的影响。因此,程序不许永远不要在可能被重用的对象上加锁。
1. 不规范代码示例 (Boolean Lock Object)
private final Boolean initialized = Boolean.FALSE;
public void doSomething() {
synchronized (initialized) {
// ...
}
}
Boolean 类型不适合用来加锁,因为它只有两个值:true, false. 包含相同值的布尔字段在 JVM 中共享布尔类的惟一实例。在上面的例子上,initialized 引用与值 Boolean.FALSE 对应的实例,如果其他的代码无意中使用相同的值 (Boolean.FALSE) 加锁在一个 Boolean 字段上,lock 实例将被重用,系统可能变得无响应或死锁。
2. 不规范代码示例 (包装类型 Boxed Primitive)
private int count = 0;
private final Integer Lock = count; // Boxed primitive Lock is shared
public void doSomething() {
synchronized (Lock) {
count++;
// ...
}
}
包装类型在一定的 integer 值范围内可能使用相同的实例 (Integer 的常量池是由 - 128 至 127 组成。当我们给一个 Integer 赋的值在这个范围之类时就直接会从缓存返回一个相同的引用;而超过这个范围时,就会重新 new 一个对象。);因此,他们遭受着与 Boolean 常量相同的重用问题。当值可以表示为字节时,包装对象被重用; JVM 还会在更大范围的值内重用该包装对象。当使用与包装类 Integer 相关的内在锁时(intrinsic lock) 是不安全的;当 new Integer 对象实例时使用 (new Integer(value)) 可以使该对象实例是惟一的且不会被重用。通常,任何包含已装箱值 (boxed value) 的数据类型上的锁都是不安全的.
2.1 兼容的解决方案 (Integer)
使用私有锁对象习惯语法的变体,使用 doSomething() 方法新建 Integer 实例用来加锁同步。
private int count = 0;
private final Integer Lock = new Integer(count);
public void doSomething() {
synchronized (Lock) {
count++;
// ...
}
}
当显式构造整数对象时,它有一个惟一的引用和它自己的内部锁,它不仅与其他 Integer 对象不同,而且与具有相同值的 boxed Integer 也不同。虽然这是一个可接受的解决方案,但它可能会导致维护问题,因为开发人员可能错误地认为装箱整数 (boxed integers) 也是适当的锁对象。更合适的解决方案应如此规则的最终兼容解决方案中所述,对 private final lock object 进行同步。
3. 不规范代码示例 (字符串对象 Interned String Object)
private final String lock = new String("LOCK").intern();
public void doSomething() {
synchronized (lock) {
// ...
}
}
根据 Java API class java.lang.String 文档,当调用 intern() 方法时,如果池中已经包含了由 equals(object) 方法确定的与此字符串对象相等的字符串,则返回池中的字符串。否则,将此字符串对象添加到池中,并返回对该字符串对象的引用。
因此,一个 Interned 字符串对象的行为就像 JVM 中的一个全局变量。 正如在这个不规范代码示例中所演示的,即使对象的每个实例都维护自己的锁字段,这些字段也都引用一个相同字符串常量。使用 String 常量用于加锁与使用 Boolean 常量加锁具有相同的重用问题。
4. 不规范的代码 (字符串常量 String Literal)
// This bug was found in jetty-6.1.3 BoundedThreadPool
private final String lock = "LOCK";
public void doSomething() {
synchronized (lock) {
// ...
}
}
String Literal 是常量,并且自动被 interned。因此,这个示例与前面的不兼容代码示例面临相同的陷阱。
4.1 兼容的解决方案 (String Literal)
private final String lock = new String("LOCK");
public void doSomething() {
synchronized (lock) {
// ...
}
}
字符串实例与字符串 literal 不同。实例有一个惟一的引用和它自己的固有锁 (intrinsic lock),此示例与其他字符串对象实例或 literal 不同。不过,更好的方法是在 private final lock 对象上同步,如下面的兼容解决方案所示。
4.2 兼容的解决方案 (Private Final Lock Object)
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
// ...
}
}