第3章
Immutable Object(不可变对象)模式
3.1 Immutable Object模式简介
在多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁。而锁本身又会带来一些问题和开销。Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线程安全,又避免引入锁可能带来的问题和开销。
在多线程环境中,一个对象常常会被多个线程共享。在这种情况下,如果存在多个线程并发地修改该对象的状态或者一个线程访问该对象的状态而另一些线程试图修改该对象的状态,则我们不得不做一些同步访问控制以保证数据一致性。而这些同步访问控制,如显式锁(Explicit Lock)和CAS(Compare and Swap)操作,会带来额外的开销和问题,如上下文切换、需要等待和ABA问题等。Immutable Object模式的意图是通过使用对外可见的状态不可变的对象(即Immutable Object),使得被共享对象“天生”具有线程安全性,而无须进行额外的同步访问控制。这样既保证了数据一致性,又避免了同步访问控制所产生的额外开销和问题,也简化了编程。
所谓状态不可变的对象,即一经创建,其对外可见的状态就保持不变的对象,例如Java中的String和Integer。这一点固然容易理解,但还不足以指导我们在实际工作中运用Immutable Object模式。下面看一个典型的应用场景,这不仅有助于我们理解该设计模式,也有助于我们在实际环境中运用它。
一个车辆管理系统要对车辆的位置信息进行跟踪,我们可以对车辆的位置信息建立如清单3-1所示的模型。
清单3-1 状态可变的位置信息模型(非线程安全)
当系统接收到新的车辆坐标数据时,需要调用Location的setXY方法来更新位置信息。显然,清单3-1中的setXY方法是非线程安全的,因为对坐标数据x和y的写操作不是一个原子操作。在setXY方法被调用时,如果在x写入完毕而y开始写之前有其他线程来读取位置信息,则该线程可能读到一个被跟踪车辆根本不曾经过的位置。为了使setXY方法具备线程安全性,我们需要借助锁进行访问控制。虽然被跟踪车辆的位置信息总是在变化,但是我们也可以将位置信息建模为状态不可变的对象,如清单3-2所示。
清单3-2 状态不可变的位置信息模型
在使用状态不可变的位置信息模型时,如果车辆的位置发生变动,则更新车辆的位置信息是通过替换整个表示位置信息的对象(即Location实例)来实现的[1],如清单3-3所示。
清单3-3 在使用状态不可变对象的情况下更新车辆的位置信息
因此,所谓状态不可变的对象并非指被建模的现实世界实体的状态不可变,而是我们建模时的一种策略:现实世界实体的状态总是在变化,但我们可以用状态不可变的对象来对这些实体进行建模。