通过封装简化线程安全类的实现过程,因为封装之后,能够访问被封装对象的所有代码路径都是已知的,即通过封装限制对象被访问的方式。
ArrayList -> Collections.SynchronizedList
HashMap -> Collections.SynchronizedMap
这些类把线程不安全类封装到自己内部,然后将所有的接口方法都实现为同步方法(加上synchronized关键字修饰),并将调用请求转发到底层容器上(就是调用它封装进去的线程不安全类的相应方法),相当于给线程不安全类所有暴露在外的线程不安全方法都加上了synchronized修饰,是装饰者模式的一种应用。
就是把类中所有能访问对象可变状态的方法都加上 synchronized 修饰(简单粗暴),虽然简单,但是一旦被加锁的方法是一个费时操作,会影响应用程序的性能甚至出现错误。以下是一个 Java 监视器模式的典型例子:
/* Java监视器模式的典型例子 */
public class Counter {
private long value = 0;
public synchronized long getValue() {
return value;
}
public synchronized long increment() {
if (value == Long.MAX_VALUE) {
throw new IllegalStateException("counter overflow");
}
return ++value;
}
}
接下来,我们将使用下面这个车辆追踪器的示例对本节内容进行讲解,下面的这个只是一个初级版,在之后讲解了新的方法之后,我们会在这个初级的版本上得到两种进化版本。
首先,我们有一个线程不安全的 Point 类,用来表示车辆的坐标。
public class MutablePoint {
public int x, y;
public MutablePoint() {
x = 0;
y = 0;
}
public MutablePoint(MutablePoint p) {
this(p.x, p.y);
}
public MutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
}
先使用 Java 监视器模式,即简单粗暴的在所有会改变 MonitorVehicleTracker 的 locations 域的方法上都加上 synchronized 修饰,来达到线程安全的目的。
public class MonitorVehicleTracker {
private final Map<String, MutablePoint> locations;
public MonitorVehicleTracker(Map<String, MutablePoint> locations) {
this.locations = deepCopy(locations);
}
public synchronized Map<String, MutablePoint> getLocations() {
// 当locations比较大时,这步是一个耗时操作,会长时间占用锁
// 会出现车辆位置已变,但返回信息保持不变的错误
return deepCopy(locations);
}
public synchronized MutablePoint getLocation(String id) {
MutablePoint loc = locations.get(id);
return loc == null ? null : new MutablePoint(loc);
}
public synchronized void setLocation(String id, int x, int y) throws IllegalAccessException {
MutablePoint loc = locations.get(id);
if (loc == null) {
throw new IllegalAccessException("No such ID: " + id);
}
loc.x = x;
loc.y = y;
}
// 当locations.size()比较大时,这个方法将会是一个十分费时的操作
public static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) {
Map<String, MutablePoint> result = new HashMap<String, MutablePoint>();
for (String id : m.keySet()) {
result.put(id, m.get(id));
}
return result;
}
}
这个车辆追踪器最大的问题就是 Point 类是一个易变的线程不安全类,这导致我们不得不在 MonitorVehicleTracker 中加入大量的同步代码,所以我们考虑从修改 Point 类入手(所以说,构建大的线程安全模块,应该从构建小的线程安全模块入手),对于这个错误,我们有两种解决思路:
- 直接把 Point 变为一个不可变对象;
- 构建一个可变但是线程安全的 Point 类,即给 Point 类中的 get 和 set 方法上加上同步,然后我们在 MonitorVehicleTracker 中就不用再使用同步了,相当于缩小了同步代码块的大小。
我们修改 Point 类如下:
public class ImmutablePoint {
public final int x, y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
}
我们可以在车辆追踪器中这样使用:
public class DelegatingVehicleTracker {
private final Map<String, ImmutablePoint> locations;
private final Map<String, ImmutablePoint> unmodifiableMap;
public DelegatingVehicleTracker(Map<String, ImmutablePoint> pointMap) {
// 通过使用ConrentHashMap来保证locations的读写安全
locations = new ConcurrentHashMap<String, ImmutablePoint>(pointMap);
unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String, ImmutablePoint> getLocations() {
return unmodifiableMap;
}
public ImmutablePoint getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y) throws IllegalAccessException { // 不同!
// 这里直接new一个新的ImmutablePoint对象替代原理的对象
if (locations.replace(id, new ImmutablePoint(x, y)) == null) {
throw new IllegalAccessException("No such ID: " + id);
}
}
}
看源码补充:
Collections.unmodifiableMap(Map<? extend K, ? extend V> m)
- 返回一个不可修改的Map,这个 Map 的实现是
Collections.unmodifiableMap(Map<? extend K, ? extend V> m)
- 这个类是 Map 的线程安全装饰类,具体实现为把传入的 Map m 保存在自己的域中,然后把所有的能修改该 Map 的方法的实现改成:
throw new UnsupportedOperationException();
把 Point 变成是线程安全的可变类:
public class SafePoint {
private int x, y;
public SafePoint(int[] a) {
this(a[0], a[1]);
}
public SafePoint(SafePoint point) {
this(point.get());
}
public SafePoint(int x, int y) {
this.x = x;
this.y = y;
}
public synchronized int[] get() {
return new int[]{x, y};
}
public synchronized void set(int x, int y) {
this.x = x;
this.y = y;
}
}
PublishingVehicleTracker 实现:
public class PublishingVehicleTracker {
private final Map<String, SafePoint> locations;
private final Map<String, SafePoint> unmodifiableMap;
public PublishingVehicleTracker(Map<String, SafePoint> pointMap) {
locations = new ConcurrentHashMap<String, SafePoint>(pointMap);
unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String, SafePoint> getLocations() {
return unmodifiableMap;
}
public SafePoint getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y) throws IllegalAccessException { // 不同!
// 因为Point已经改成线程安全的了,我们可以通过Point自己的set和get方法放心大胆的修改它
SafePoint loc = locations.get(id);
if (loc == null) {
throw new IllegalAccessException("No such ID: " + id);
}
loc.set(x, y);
}
}
不好,因为有的状态不对子类公开。
在装饰类里放个线程安全的 List,然后在写个加锁的扩展方法 putIfAbsent,注意要用 list 当锁,不然锁不一致!
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x) {
synchronized (list) {
boolean contains = list.contains(x);
if (!contains) {
list.add(x);
}
return !contains;
}
}
}
**缺点:**通过添加一个原子操作的扩展类是脆弱的,因为它将类的加锁代码分布到了多个类中
-
List 原有方法的加锁代码在 Collections.SynchronizedList 的代码中
-
新加的 putIfAbsent 方法的加锁代码在 ListHelper 中
将 List 的操作委托给底层的 list 实例,并把这些方法都实现为 synchronized 的,然后添加一个新的 synchronized 方法putIfAbsent,然后客户代码不会再直接使用 list 对象,而是通过 ImproveList 来操纵它,这样加锁代码就都在一个类中了,同时底层的 list 实现也不用必须是线程安全的。(就是要写的代码有点多……)
public class ImproveList<E> implements List<E> {
private final List<E> list;
public ImproveList(List<E> list) {
this.list = list;
}
public synchronized boolean putIfAbsent(E x) {
boolean contains = list.contains(x);
if (!contains) {
list.add(x);
}
return !contains;
}
/* 剩下的是List本来的方法,都加上synchronized,然后内部调用底层list实现 */
@Override
public synchronized int size() {
return list.size();
}
// ...
}