1.定义

不可变对象 (Immutable Object) 是指一经创建其状态就保持不变的对象。不可变对象也具有固有的线程安全性,因此不可变对象也可以像无状态对象那样被多个线程共享,而这些线程访问这些共享对象的时候无须加锁。当不可变对象所建模的现实实体的状态发生变化时,系统通过创建新的不可变对象实例来进行反映。
一个严格意义上的不可变对象要同时满足以下所有条件。

  • 类本身使用 final 修饰:这是为了防止通过创建子类来改变其定义的行为。
  • 所有字段都是用 final 修饰的:使用 final 修饰不仅仅是从语义上说明被修饰字段的值不可改变;更重要的是这个语义在多线程环境下保证了被修饰字段的初始化安全,即 final 修饰的字段在对其他线程可见时,它必定是初始化完成的。
  • 对象在此初始化过程中没有逸出 (Escape) : 防止其他类(如该类的内部匿名类)在对象初始化过程中修改其状态。
  • 任何字段,若其引用了其他状态可变的对象(如集合、数组等),则这些字段必须是 private 修饰的,并且这些字段值不能对外暴露。若有相关方法要返回这些字段值,则应该进行防御性复制(Defensive Copy)。

有时创建严格意义上的不可变对象比较难,此时不妨考虑使用等效或者近似的不可变对象,这也同样有利于发挥不可变对象的优势。

2.不可变对象的典型应用场景

  • 被建模对象的状态变化不频繁。这种场景下可以设置一个工作者线程用于在被建模对象状态变化时创建新的不可变对象。而其他线程则仅读取不可变对象的状态。此场景下的一个小技巧是采用 volatile 关键字修饰引用不可变对象的变量,这样既可以避免使用锁(如synchronized) 又可以保证可见性。
  • 同时对一组相关的数据进行写操作,因此需要保证原子性。此场景为了保证操作的原子性,通常的做法是使用锁。而此时应用不可变对象,我们既可以保障原子性又可以避免锁的使用,从而既简化了代码又提高了代码运行效率。
  • 使用不可变对象作为安全可靠的 Map 键 (Key) 。设 someKey 为任意一个状态可变对象, someValue 为任意一个对象, map 为 Map<K,V>接口的任意一个实例(比如 HashMap 实例)。在 map.put(someKey,someValue)被调用之后,如果 someKey 的内部状态变化导致 someKey.hashCode() 的返回值产生变化,那么map.get(someKey) 调用将无法返回someValue, 即使在此期间无任何线程执行map.remove(someKey) ! 而如果 someKey 是一个不可变对象,那么 someKey.hashCode()返回值恒定,因此 map.get(someKey)调用总是可以返回 someValue (除非中途 map.remove(someKey)被调用过)。因此,不可变对象非常适宜用作Map的键 。