6.不可变对象(Immutable Object)
如果一个对象在创建之后就不可以改变它的状态,则这个对象被认为是不可变的(immutable)。最大化依赖不可变对象来作为创建简单,可靠代码的一种正确的策略,被广泛接受。
不可变对象在并发程序中尤其有用,因为它们不可以改变状态,所以它们不会在线程交织中崩溃或是被看到不一致的状态。
程序员进程不愿意使用不可变对象,他们担心创建新对象比更新一个对象要付出更多代价。创建新对象的影响往往被过高估计,这个代价有时可以被不可变对象的效率所抵消。这些包括减少垃圾回收的额外开销和为了保护可变对象崩溃而增加的额外代码的开销。
6.1 一个同步对象的例子
类SynchronizedRGB,定义了一个表示颜色的对象。它的一个对象表示一个颜色,其有三个整数表示的三原色的值决定,并且用一个字符串表示颜色名。
public class SynchronizedRGB { // Values must be between 0 and 255. private int red; private int green; private int blue; private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public SynchronizedRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public void set(int red, int green, int blue, String name) { check(red, green, blue); synchronized (this) { this.red = red; this.green = green; this.blue = blue; this.name = name; } } public synchronized int getRGB() { return ((red << 16) | (green << 8) | blue); } public synchronized String getName() { return name; } public synchronized void invert() { red = 255 - red; green = 255 - green; blue = 255 - blue; name = "Inverse of " + name; } }
|
SynchronizedRGB必须小心使用,避免出现不一致的状态,例如,一个执行下面代码的线程:
SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); ... int myColorInt = color.getRGB(); //语句 1 String myColorName = color.getName(); //语句 2 |
如果另外一个线程在语句1和语句2之间调用color.set。则myColorInt的值就和myColorName的值不匹配。为了避免这个结果,这两个语句必须要帮定在一起:
synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); } |
这个不一致只在可变对象上产生——在SynchronizedRGB的不可变版本上是不会出现问题的。
6.2 定义不可变对象的策略
下面的规则定义了创建不可变对象的简单规则。不是所有的不可变类都要遵循这里的规则。这并不意味着这些类的创建者可以草率的——他们可能有很好的理由相信他们的类的实例创建后不会改变状态。然而,这样的策略需要富有经验的分析,不时新手能做到的。
1. 不要提供“setter”方法——修改字段的方法或字段引用的对象
2. 让所有的字段时“final”和“private”的
3. 不要让子类继承方法。最简单的方法就是声明类为final。更精巧的做法是让构造器是“private”的,并用工厂方法创建实例。
4. 如果实例字段中包含指向可变对象的引用,不要允许那些对象被改变:
a) 不要提供改变可变对象的方法
b) 不要共享指向引用对象的方法。从不存储传递到构造器的外部的可变对象的引用;如果有必要,创建一个拷贝,存储拷贝的引用。相似的,创建内部可变对象的拷贝来避免在方法中直接返回原版对象。
将这些规则应用到SynchronizedRGB,导致了以下的步骤:
1. 这个类里有两个“setter”方法。第一个,set,可以任意改变对象,在这个类的不变版本中没有存在的必要了。第二个,invert,可以修改成创建新的对象,而不是修改原来的对象。
2. 所有的字段都已经私有了;他们被进一步的修饰成final。
3. 类本身被声明成了final。
4. 只有一个指向对象的引用,这个对象本身是不可变的。所有,没有必要对包含的可变对象采取措施。
经过这些步骤,我们得到了ImmutableRGB,如下:
final public class ImmutableRGB { // Values must be between 0 and 255. final private int red; final private int green; final private int blue; final private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public ImmutableRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public int getRGB() { return ((red << 16) | (green << 8) | blue); } public String getName() { return name; } public ImmutableRGB invert() { return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverse of " + name); } }
|