不可变对象(Immutable Objects)(Concurrency tutorial 六)
不可变对象(Immutable Objects)(Concurrency tutorial 6)
不可变对象(Immutable Objects)
如果一个对象的状态在其创建之后不能被改变,那我们就说它是不可变对象。在多线程编程中,最大程度地使用不可变对象可以实现简单又可靠的代码。
不可变对象在并发应用中特别有用。因为它们不会改变状态,它们在遇到线程干扰和不一致访问时也不会出错(译注:因为每个线程在试图改变一个对象时,实际上是得到了该对象在最新状态下的拷贝,例如String就是不可变对象)。
程序员经常不愿使用不可变对象,因为他们担心拷贝对象会降低性能。实际上,创建对象的资源耗费常被高估(overestimated),实际上这样的资源耗费完全可以被不可变对象提高的效率抵消掉。使用不可变对象可以降低因垃圾回收产生的对象管理开支,消除为保护可变对象不被污染的额外代码。
下面几个小节里将创建一个状态可变的类,然后演化成一个状态不可变的类。以此描述转换要遵守的规则以及不可变对象相对于可变对象的优势。
一个同步类的例子
SychronizedRGB类定义了表示颜色对象的集合。每一个对象代表一种颜色,每一种颜色用三个表示基本色的整数和一个描述颜色名称的字符串表示。
SynchronizedRGB必须谨慎使用来避免读写不一致。譬如,一个线程这样执行时:
另一个线程在Statement 1之后,Statement 2之前调用color.set。那么myColorInt的值就与对应的myColorName不一致。为了避免这样的结果,两条语句必须绑定执行:
这种不一致只会在可变对象上发生 ——对于不可变版本的SynchronizedRGB不会有问题。
定义不可变对象的一种策略
下面的规则说明了创建不可变对象的一种简单策略。并不是所有的不可变类都要遵循下面的规则,也并不是说这些类的创建者都是糊涂蛋——他们有充分的理由相信这些类的实例在创建之后不会改变状态。然而,这样的策略需要复杂的分析,初学者慎用。
将策略应用到SynchronizedRGB需要一下几步:
经过一些改变,我们得到了ImmutableRGB:
不可变对象(Immutable Objects)
如果一个对象的状态在其创建之后不能被改变,那我们就说它是不可变对象。在多线程编程中,最大程度地使用不可变对象可以实现简单又可靠的代码。
不可变对象在并发应用中特别有用。因为它们不会改变状态,它们在遇到线程干扰和不一致访问时也不会出错(译注:因为每个线程在试图改变一个对象时,实际上是得到了该对象在最新状态下的拷贝,例如String就是不可变对象)。
程序员经常不愿使用不可变对象,因为他们担心拷贝对象会降低性能。实际上,创建对象的资源耗费常被高估(overestimated),实际上这样的资源耗费完全可以被不可变对象提高的效率抵消掉。使用不可变对象可以降低因垃圾回收产生的对象管理开支,消除为保护可变对象不被污染的额外代码。
下面几个小节里将创建一个状态可变的类,然后演化成一个状态不可变的类。以此描述转换要遵守的规则以及不可变对象相对于可变对象的优势。
一个同步类的例子
SychronizedRGB类定义了表示颜色对象的集合。每一个对象代表一种颜色,每一种颜色用三个表示基本色的整数和一个描述颜色名称的字符串表示。
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(); //Statement 1 String myColorName = color.getName(); //Statement 2
另一个线程在Statement 1之后,Statement 2之前调用color.set。那么myColorInt的值就与对应的myColorName不一致。为了避免这样的结果,两条语句必须绑定执行:
synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); }
这种不一致只会在可变对象上发生 ——对于不可变版本的SynchronizedRGB不会有问题。
定义不可变对象的一种策略
下面的规则说明了创建不可变对象的一种简单策略。并不是所有的不可变类都要遵循下面的规则,也并不是说这些类的创建者都是糊涂蛋——他们有充分的理由相信这些类的实例在创建之后不会改变状态。然而,这样的策略需要复杂的分析,初学者慎用。
- 不要提供setter方法(可能改变类数据成员的方法或者改变数据成员指向的对象的状态的方法)。
- 所有的数据成员修饰为private final。
- 不允许子类继承父类方法。最简单的方法就是将类声明为final。更复杂的方法是将构造器声明为private。使用工厂方法创建实例。
- 如果数据成员中存在有指向可变对象的,禁止这些被指向的对象状态被改变。
- 不要提供方法改变可变对象。
- 不要共享指向可变对象的引用。不要在构造器中存储指向可能发生改变的对象的引用。如果必要,创建这些对象的拷贝,转而存储这些拷贝的引用。同样地,当需要返回内部可变对象时,返回其拷贝,避免返回原始的内部对象引用。
将策略应用到SynchronizedRGB需要一下几步:
- 类里有两个setter方法。第一个set方法能随意转换对象状态,这在不可变类中根本不允许存在(and has no place in an immutable version of the class)。第二个,invert,可以改造为返回修改对象的拷贝,而不是修改当前对象。
- 所有的字段都是private的;它们应进一步地修饰为final。
- 类本身应修饰为final。
- 只有一个数据成员指向一个对象,且对象是其本身。因为其本身是不可变的,所以就没有必要再采取措施防止其包含的可变对象发生改变。
经过一些改变,我们得到了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); } }