读读源码-装饰器模式与SynchronizedCollection
装饰器模式,动态的给一个对象添加一些额外的职责,比生成子类更为灵活。
继承主要有单继承的局限性和可能产生类爆炸的后果。
SynchronizedCollection主要利用装饰器模式解决了一些集合线程不安全的问题。通过sync给对应的方法加锁,并让其保持原子性
static class SynchronizedCollection<E> implements Collection<E>, Serializable { @java.io.Serial private static final long serialVersionUID = 3053995032091335093L; @SuppressWarnings("serial") // Conditionally serializable final Collection<E> c; // Backing Collection @SuppressWarnings("serial") // Conditionally serializable final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection<E> c) { this.c = Objects.requireNonNull(c); mutex = this; } SynchronizedCollection(Collection<E> c, Object mutex) { this.c = Objects.requireNonNull(c); this.mutex = Objects.requireNonNull(mutex); } }
这里除了原本的集合类,还包含了一个mutex(互斥锁),基于sync和mutex,可以保证在多个线程尝试修改集合时,每次只有一个线程能够完成这些操作。
一些方法的举例:在原本方法的基础上增加了sync给方法上个锁。这就是装饰器模式
public int size() { synchronized (mutex) {return c.size();} } public boolean isEmpty() { synchronized (mutex) {return c.isEmpty();} } public boolean contains(Object o) { synchronized (mutex) {return c.contains(o);} } public Object[] toArray() { synchronized (mutex) {return c.toArray();} }
问题:原子性
上述情况已经保证了线程安全?在部分复合操作场景下,例如下文,仍然可能会导致线程不安全:
线程AB同时走到objects.isEmpty的时候,A先执行,发现是空的。
此时,B再执行,两者都会发现objects是空的,也就是说后面都会去执行一次add。
Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>()); if (objects.isEmpty()) { objects.add(1); }
如何解决:再套一层锁,保持操作原子性
Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>()); synchronized (objects) { if (objects.isEmpty()) { objects.add(1); } }