引言
对象的组合,是《Java Concurrency in Practice》中第四章引入的课题。这并不是一个并发的概念。
为了可以将现有的线程安全组件组合为更大规模的组件或程序,而不是每次内存访问都进行分析以确保程序是线程安全的。这一章将介绍一些组合模式,这些模式可以更容易的使一个类成为线程安全的类,并且维护性更强。
一、设计线程安全的类
为了在不对整个程序进行分析的情况下就可以得出一个类是否是线程安全类的结论,总结了设计线程安全类的三个基本要素。
找出构成对象状态的所有变量。
找出约束状态变量的不变性条件。
建立对象状态的并发访问管理策略。
对象的状态指的是那些基本类型的变量。如果在对象的域中引用了其他对象,那么该对象的状态将包含被引用对象的域。
1.1 什么是同步策略?
同步策略的意思是保证对象不变性条件和后验条件的前提下协同各个访问操作。是不变性条件、线程封闭、加锁机制、锁保护的相关概念的一个统称。
1.2 什么是不变性条件?
不变性条件指的是在对象状态空间内的一种逻辑约束。
状态空间简单的说就是对象的状态所有的可能值。而不变性条件就是人为规定的在状态空间内只能取哪些值。比如:
public class Counter {private long value = 0;public long increment() {if (value == Long.MAX_VALUE)throw new IllegalStateException("计数器溢出");return ++value;}
}
Counter类的对象有一个long类型的value,那么状态空间就是Long.MIN_VALUE 到 Long.MAX_VALUE之间所有的整型,但是由于该类的方法只提供了一个增长的方法,而value的初始值又是0,因此,对于这个类的不变性条件,就是value不能是负数。
1.3 状态迁移
对象的状态通过相关的方法产生了变化,这就是状态迁移。
1.4 后验条件
人为规定的状态迁移后的状态的有效性条件。比如上面的代码中,如果此时value是17,那么执行increment()后value一定要等于18。那么这里的后验条件就是状态改变后的值比状态改变前大1。
另外,当下一个状态需要依赖当前状态时,这个操作就必须是复合操作。但是,并不是所有操作都会在状态迁移上施加限制,例如,温度变量、彩票变量。
1.5 不变形条件与原子性
如果,不变性条件包含多个变量,那么将产生原子性的需求:这些相关的变量必须在单个原子操作中进行读取或更新。简单地说,就是不能先更新一个变量,然后释放锁,再获取锁,再去更新另一个相关变量。因为多个变量构成的不变性条件是整体性的,如果分开更新相关的状态,那么在中间的某个时刻必然会导致对象处于失效状态。
1.6 先验条件
简单地说就是,必须满足某种要求程序才能继续执行的条件。它属于一种依赖的状态。
单线程中的某个操作如果无法满足先验条件,则必然失败;多线程下可能会由于其他线程执行的操作而变为真。
并发程序中一定要等到先验条件为真,然后再执行该操作。这就引出了另一个相关的机制:Java的线程通信机制。比如等待和通知、阻塞等。
1.7 状态的所有权
对象对它封装的状态拥有所有权。所有权意味着控制权。
二、实例封闭(Instance Confinement)
如果某个对象不是线程安全的,有很多手段可以使它在多线程程序中正常使用。可以使用线程封闭技术确保这个对象只能由单个线程访问;或者通过锁来保护对象的所有访问。
其实,实例封闭技术在日常开发中经常使用。简单的说,对象A作为一个私有成员封装在了对象B中,那么对象A就是一个封闭的实例,A的访问也可以得到有效的控制。
例如下面的程序中,PersonSet的状态由HashSet来管理,而HashSet并非线程安全的。但由于mySet是私有的并且不会逸出,因此HashSet被封闭在PersonSet中。唯一能访问mySet的代码路径是addPerson与containsPerson,在执行它们时都要获得PersonSet上的锁。PersonSet的状态完全由它的内置锁保护,因而PersonSet是一个线程安全的类。
public class PersonSet {private final Set<Person> mySet = new HashSet<>();public synchronized void addPerson(Person p) {mySet.add(p);}public synchronized boolean containsPerson(Person p) {return mySet.contains(p);}
}
2.1 监视器模式
监视器模式并不是Java的GoF 23设计模式。什么是监视器模式?将对象的所有可变状态都封装起来,并且只能通过内置锁来访问,这就是监视器模式。而内置锁synchronized也成为监视器或监视器锁。
Java监视器模式只是一种编吗约定:对于任何一种锁对象,只要自始至终都使用该锁对象,都可以用来保护对象的状态。
监视器模式的两个代表:Vector和Hashtable。
2.2 私有锁对象
私有锁对象而不是对象的内置锁,可以将锁封装起来,使客户代码只能通过共有方法来访问锁。
public class PrivateLock {private final Object myLock = new Object();@GuardBy("myLock")Widget widget;void someMethod() {synchronized (myLock) {// 访问或修改Widget的状态}}
}