对封装好的jar包来说,使用它的代码都是客户端。
客户端加锁是指,对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户端代码。要使用客户端加锁,必须知道对象X使用的是哪一种锁。
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<>());
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if(absent) {
list.add(x);
}
return absent;
}
}
上边这段扩展Collections的客户端代码不是线性安全的,因为list和ListHelper使用了不同的内置锁,putIfAbsent相对于List的其他操作来说并不是原子的,因此无法保证当putIfAbsent执行时另一个线程不会修改链表。要使这个方法正确执行,必须使List和ListHelper使用同一把锁。在Collections的文档中指出,通过使用封装容器的内置锁来支持客户端加锁。
public boolean putIfAbsent(E x) {
synchronized(list) {
boolean absent = !list.contains(x);
if (absent) {
list.add(x);
}
return absent;
}
}
客户端加锁机制和扩展类机制有很多共同点,二者都是将派生类的行为与基类的实现耦合在一起,都会破坏实现的封装性。
当为现有的类增加一个原子操作时,使用组合是更好的选择。例如,通过实现List接口,新增功能putIfAbsent,这种方式比客户端加锁更健壮。事实上,组合使用了Java监视器模式来封装现有的List,并且只要在类中拥有指向底层的List的唯一外部引用,加上ImprovedList提供的一致的同步机制,就能保证线程安全性。
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 x) { boolean contains = list.contains(x); if(!contains) { list.add(x); } return !contains; } @Override public int size() { return 0; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return false; } @Override public Iterator<T> iterator() { return null; } @Override public Object[] toArray() { return new Object[0]; } @Override public <T1> T1[] toArray(T1[] a) { return null; } @Override public boolean add(T t) { return false; } @Override public boolean remove(Object o) { return false; } @Override public boolean containsAll(Collection<?> c) { return false; } @Override public boolean addAll(Collection<? extends T> c) { return false; } @Override public boolean addAll(int index, Collection<? extends T> c) { return false; } @Override public boolean removeAll(Collection<?> c) { return false; } @Override public boolean retainAll(Collection<?> c) { return false; } @Override public void clear() { } @Override public T get(int index) { return null; } @Override public T set(int index, T element) { return null; } @Override public void add(int index, T element) { } @Override public T remove(int index) { return null; } @Override public int indexOf(Object o) { return 0; } @Override public int lastIndexOf(Object o) { return 0; } @Override public ListIterator<T> listIterator() { return null; } @Override public ListIterator<T> listIterator(int index) { return null; } @Override public List<T> subList(int fromIndex, int toIndex) { return null; } }