滥用同步组件是造成并发问题的一个普遍性根源,在一个可能被重用的对象上加锁可能造成死锁或者其他不确定的影响。因此,程序不许永远不要在可能被重用的对象上加锁。
不规范代码示例(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实例将被重用,系统可能变得无响应或死锁。
不规范代码示例(包装类型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)的数据类型上的锁都是不安全的.
兼容的解决方案(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进行同步。
不规范代码示例(字符串对象 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常量加锁具有相同的重用问题。
不规范的代码(字符串常量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。因此,这个示例与前面的不兼容代码示例面临相同的陷阱。
兼容的解决方案(String Literal)
private final String lock = new String("LOCK");
public void doSomething() {
synchronized (lock) {
// ...
}
}
字符串实例与字符串literal不同。实例有一个惟一的引用和它自己的固有锁( intrinsic lock),此示例与其他字符串对象实例或literal不同。不过,更好的方法是在private final lock对象上同步,如下面的兼容解决方案所示。
兼容的解决方案 (Private Final Lock Object)
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
// ...
}
}