同步容器与并发容器

1.同步容器

1.1 什么是同步容器

同步容器是指那些在容器内部已经同步化了,使我们在并发操作使用容器的时候不需要进行手动同步了。

1.2 同步容器的分类

同步容器可以分为两大类:普通类和内部类
普通类
主要是Vector、Stack、HashTable
普通类其实现的方式是通过在方法上添加synchronized关键字来进行实现的。就比如,Vector容器的add,set,get方法,其源码如下:

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }
    
    public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

从上面vector的源码可以看出其主要是通过synchronized关键字的方式实现的。
内部类
是指Collections创建的内部类,比如Collections.SynchronizedList、 Collections.SynchronizedSet等
内部类是把原有的容器进行包装(通过this.list = list直接指向需要同步的容器),然后局部加锁,这样一来,就生成了线程安全的类;源码如下:

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        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);
        }

        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();}
        }
        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {return c.toArray(a);}
        }

        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        public boolean add(E e) {
            synchronized (mutex) {return c.add(e);}
        }
        public boolean remove(Object o) {
            synchronized (mutex) {return c.remove(o);}
        }

        public boolean containsAll(Collection<?> coll) {
            synchronized (mutex) {return c.containsAll(coll);}
        }
        public boolean addAll(Collection<? extends E> coll) {
            synchronized (mutex) {return c.addAll(coll);}
        }
        public boolean removeAll(Collection<?> coll) {
            synchronized (mutex) {return c.removeAll(coll);}
        }
        public boolean retainAll(Collection<?> coll) {
            synchronized (mutex) {return c.retainAll(coll);}
        }
        public void clear() {
            synchronized (mutex) {c.clear();}
        }
        public String toString() {
            synchronized (mutex) {return c.toString();}
        }
        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> consumer) {
            synchronized (mutex) {c.forEach(consumer);}
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            synchronized (mutex) {return c.removeIf(filter);}
        }
        @Override
        public Spliterator<E> spliterator() {
            return c.spliterator(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> stream() {
            return c.stream(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> parallelStream() {
            return c.parallelStream(); // Must be manually synched by user!
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

可以看到他是对其对西昂mutex进行加锁,来保证方法的同步。
同步类和内部类的区别

区别 普通类 内部类
锁的对象 不可指定,只能this 可指定,默认this
锁的范围 方法体(包括迭代) 代码块(不包括迭代)
适用范围 窄-个别容器 广-所有容器

1.3 同步容器的优缺点

优点

  • 并发编程中,独立操作是线程安全的。比如只是执行一个add,get操作

缺点

  • 性能差,基本上每个操作都进行了加锁
  • 对于复合操作其还不是性能安全的

2.并发容器

并发容器主要是为了用来改善同步容器的性能的,因为同步容器对于每一步操作都进行了加锁,因此导致了同一时刻只能有一个线程访问容器,所以就导致了性能很差。因此为了提升同步容器的性能就引入了并发容器。其将对共享资源在同一时刻的操作由串行改为了并行。
并发容器主要是通过分段锁,非阻塞的CAS算法等技术来对容器进行优化,主要的容器有ConcurrentHashMap、CopyOnWriteArrayList等。
ConcurrentHashMap
它是采用了分段锁的机制,使锁的粒度更小,允许线程并发的进行操作。
CopyOnWriteArrayList
其可以保证线程安全,保证读线程之间不会被阻塞。其是通过Copy-On-Write(COW)思想来实现的,COW即写时复制的思想,也就是在像一个容器中添加元素时,先进行复制原容器,然后在复制的容器中进行添加数据,添加好之后,再指向复制的容器。
可以通过其add方法可以看到:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 加锁,保证只有一个线程进行数组负值
        lock.lock();
        try {
            // 获取旧的数据的引用
            Object[] elements = getArray();
            int len = elements.length;
            // 复制新的数据
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 指向复制的数据
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

其在进行添加元素的时候进行加锁,保证只有一个线程进行数组复制,然后通过复制一个新的列表来进行添加的操作,最后再指向新的列表。进而保证了读写操作不是再同一个数据列表中进行操作的。
从上面的描述我们就可以看出COW会存在内存占用问题。同时其数据一致性也是保证的最终数据一致性。

posted @   _mcj  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示