Java中的并发容器一

一:获取线程安全的集合

   

    早期JDK中除了vector和hashtable是线程安全的,Set接口的实现类还没有一个线程安全的Set。

    我们通过下面的方法获取到线程安全的Set。

    Collections中的工具方法

   Collections 工具类中提供了多个可以获得线程安全集合的方法。(JDK1.5以前)

•  public static <T> Collection<T> synchronizedCollection(Collection<T> c)

•  public static <T> List<T> synchronizedList(List<T> list)

•  public static <T> Set<T> synchronizedSet(Set<T> s)

•  public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

•  public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)

•  public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

 

以ArrayList为例

	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		
		List<Integer> safeList = Collections.synchronizedList(list);//线程安全的List(互斥)
	}

源代码

 public static <T> List<T> synchronizedList(List<T> list) {

        return (list instanceof RandomAccess ?

                new SynchronizedRandomAccessList<>(list) :

                new SynchronizedList<>(list));

}

Collections内的静态内部类static class SynchronizedRandomAccessList<E>

 

部分源代码

public E get(int index) {

            synchronized (mutex) {return list.get(index);}

        }

public E set(int index, E element) {

            synchronized (mutex) {return list.set(index, element);}

        }

 JDK1.2 提供,接口统一、维护性高,但性能没有提升,均以 synchonized 实现。

 

二:CopyOnWriteArrayList

        •  线程安全的 ArrayList ,加强版读写分离。

        •  写有锁,读无锁,读写之间不阻塞,优于读写锁。

        •  写入时,先 copy 一个容器副本、再添加新元素, 最后 替换引用。

        •  使用方式与 ArrayList 无异。


写操作:

 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();

        }

}

 

源码分析:①加锁②getArray()获取数组、和数组长度③拷贝原数组,长度+1④添加的元素下标赋值 ⑤将新数组地址覆盖原数组中的地址⑥解锁

为什么会对读操作没有影响?

 

读操作:

    public E get(int index) {

        return get(getArray(), index);

    }

源码分析:读操作得到的是一个副本,即时,读写线程同时进行,写操作没完成之前,读线程读到是原来数组的副本,而添加操作并未对原数组进行操作,只是拷贝了一份。(读写或不干扰)如果写操作完成时,再来一个读线程读取到的是新的数组的副本。

 

三:CopyOnWriteArraySet

•  线程安全的 Set ,底层使用 CopyOnWriteArray List 实现 。

•  唯一不同在于,使用 addIfAbsent() 添加元素,会遍历数组,

•  如存在元素,则不添加(扔掉副本)。

 

//addIfAbsent()源代码

private boolean addIfAbsent(E e, Object[] snapshot) {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            Object[] current = getArray();

            int len = current.length;

            if (snapshot != current) {

                // Optimize for lost race to another addXXX operation

                int common = Math.min(snapshot.length, len);         

                for (int i = 0; i < common; i++)

                    if (current[i] != snapshot[i] && eq(e, current[i]))

                        return false;

                if (indexOf(e, current, common, len) >= 0)

                        return false;

            }

            Object[] newElements = Arrays.copyOf(current, len + 1);

            newElements[len] = e;

            setArray(newElements);

            return true;

        } finally {

            lock.unlock();

        }

    }
    源码分析:添加时,不仅完成了模版的拷贝,还对每个元素进行去重操作。如果需要添加自定类,则需要重写Object的equals方法。

 

 

猜你喜欢

转载自blog.csdn.net/Sugar_map/article/details/79740002