1.线程安全性的委托
委托意味着,将线程安全性委托给自己的线程安全组件,但请一定注意,即便组件都是线程安全的,也不能保证类是线程安全的!上篇博文提到了“车辆追踪”的时效性问题,那么如何解决?既然问题是返回的结果为locations的一个deepcopy,那么解决办法当然是发布一个线程安全的Map对象,不需要进行复制,代码如下:
public class DelegatingVehicleTracker { private final ConcurrentMap<String, Point> locations; private final Map<String, Point> unmodifiableMap; public DelegatingVehicleTracker(Map<String, Point> points) { locations = new ConcurrentMap<>(points); unmodifiableMap = Collections.unmodifiableMap(locations); } public Map<String, Point> getLocations() { return unmodifiableMap; } public Point getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if(locations.replace(id, new Point(x, y)) == null) { throw new IllegalArgumentException("invalid vehicle name: " + id); } } }
这里,注意到locations使用了ConcurrentMap这个线程安全的Map,使用装饰器模式,将它转换为不可修改的Map后,发布没有任何线程安全问题,而且也解决了车辆位置的时效性问题:因为发布的是实时locations。这里的Point类也是线程安全的,可以直接通过getLocation()方法发布:
public class Point { public final int x, y; public Point(int x, int y) { this.x = x; this.y = y } }
这里,因为属性x, y都是final的,所以在初始化之后是不可改变的,不会出现状态不一致的情况,所以为线程安全的。final域能保证在对象引用在任意线程可见之前,final域已经被初始化过了,不会出现中间状态这种情况。当然,如果你想将Point改为可变的线程安全类也可以:
public class SafePoint { private SafePoint(int[] a) { this(a[0], a[1]); } public SafePoint(SafePoint p) { this(p.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; } }
注意,这里使用数组保存坐标,是为了使“修改”这一操作具有原子性,不能出现x坐标和y坐标修改出现不一致性。
2.在线程安全类中添加功能
一般来说,你不能直接修改原始的类(无法访问到源代码),这时如果你想添加线程安全的功能就要格外小心,不能破坏原有类的线程安全特性。看一个例子,假设我要添加一个“如果不存在则添加”的操作,这个操作一般分为两个步骤:先判断是否存在,再往里添加元素。如果不满足线程安全的要求,则可能出现在容器中保留两个相同的元素。最简单的做法,去扩展一个线程安全类:
public class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E e) { boolean absent = !contains(e); if(absent) add(x); return absent; } }
但这这种做法通常是不被推荐的,原因在于你将同步策略分布到多个单独维护的源代码文件中,如果底层改变了同步策略,那么子类的同步性会被破坏。再来看看客户端加锁方法:
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E e) { boolean absent = !list.contains(e); if(absent) list.add(e); return absent; } }
好,这里我们用装饰器模式包装了一个ArrayList,使其变成线程安全的类,然后又在putIfAbsent方法上加上了内置锁:synchronized关键字,是不是一定保证了线程安全?答案是No!
为什么?仔细看看这个类,list的同步性是靠其内置锁完成,而synchronized的同步性则是靠ListHelper的内置锁完成,这两个锁根本不是同一个锁!所以这种做法肯定不是线程安全的,putIfAbsent()相对于list的其他方法并不是原子的,接下来看看正确的做法:
public class ImprovedList<T> implements List<T> { private final List<T> list; public ImprovedList(List<T> list) { this.list = list; } public synchronized boolean putIfAbsent(T t) { boolean contains = !list.contains(t); if(contains) list.add(t); return contains; } }
这里通过组合实现了线程安全,list的唯一引用被封装在ImprovedList内,所用的锁均为ImprovedList的内置锁,通过转发请求到底层的list上,保证了线程安全性。