读读源码-装饰器模式与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);
    }
}

 

posted @ 2025-02-12 17:39  天启A  阅读(11)  评论(0)    收藏  举报