您的位置 首页 java

Javaset集合的线程安全类你知道吗?ConcurrentHashSet了解一下

从文章标题看,你可能很疑惑JDK中没有ConcurrentHashSet这个类啊,说对了,这篇文章就是分享怎样使用 ConcurrentHashMap 来试下 ConcurrentHashSet。

实现ConcurrentHashSet之前,我们先来回顾一下Set接口有哪些子类?我们先看下类图:

从上面看,set的子类有四个,其中 :HaseSet、TreeSet、LinkedHashSet.这三个都是 线程 不安全的,CopyOnWriteArraySet是 线程安全 的。

即使CopyOnWriteArraySet 是线程安全的,它也不适合大型线程安全集合的应用程序,它仅用于设置大小保持较小且读大于写的应用程序,那么为什么呢?我们从源码上进行分析

CopyOnWriteArraySet 内部是使用 CopyOnWriteArrayList 来实现的,那么我们分析 CopyOnWriteArrayList的添加元素方法。

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public  Boolean  add(E e) {
    final Reentrant lock  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();
    }
    }  

上面每次添加元素的时候都需要创建一个新容器,然后把旧容器的元素copy进去,最终把新容器的赋值给旧容器,整个的操作是在锁当中调用的,所以是线程安全的。

那么为什么不建议在并发高的应用程序中使用呢?有以下两点

(1) 性能问题:

每次修改都创建一个新数组,然后复制所有内容,如果数组比较大,修改操作又比较频繁,可以想象,性能是很低的,而且内存开销会很大。

(2) 数据一致性问题。

CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,不要使用CopyOnWrite容器。

ConcurrentHashSet 的创建

可能有的同学会将,我也可以直接使用 ConcurrentHashMap 来进行操作,没必要使用ConcurrentHashSet。事实上,这正是 ConcurrentHashSet的 实现原理,底层是用 ConcurrentHashMap来实现的。

实现起来还是很简单的,因为HashSet 的底层也是使用HashMap来实现的。

 public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>, java.io.Serializable {

    private static final long serialVersionUID = -8672117787651310382L;

    private static final Object PRESENT = new Object();

    private final ConcurrentHashMap<E, Object> map;

    public ConcurrentHashSet() {
        map = new ConcurrentHashMap<E, Object>();
    }

    public ConcurrentHashSet(int initialCapacity) {
        map = new ConcurrentHashMap<E, Object>(initialCapacity);
    }

    /**
     * Returns an iterator over the elements in this set. The elements are
     * returned in no particular order.
     *
     * @return an Iterator over the elements in this set
     * @see ConcurrentModificationException
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    /**
     * Returns the number of elements in this set (its cardinality).
     *
     * @return the number of elements in this set (its cardinality)
     */
    public int size() {
        return map.size();
    }

    /**
     * Returns <tt>true</tt> if this set contains no elements.
     *
     * @return <tt>true</tt> if this set contains no elements
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * Returns <tt>true</tt> if this set contains the specified element. More
     * formally, returns <tt>true</tt> if and only if this set contains an
     * element <tt>e</tt> such that
     * <tt>(o==null ? e==null : o.equals(e))</tt>.
     *
     * @param o element whose presence in this set is to be tested
     * @return <tt>true</tt> if this set contains the specified element
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    /**
     * Adds the specified element to this set if it is not already present. More
     * formally, adds the specified element <tt>e</tt> to this set if this set
     * contains no element <tt>e2</tt> such that
     * <tt>(e==null ? e2==null : e.equals(e2))</tt>. If this
     * set already contains the element, the call leaves the set unchanged and
     * returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }

    /**
     * Removes the specified element from this set if it is present. More
     * formally, removes an element <tt>e</tt> such that
     * <tt>(o==null ? e==null : o.equals(e))</tt>, if this
     * set contains such an element. Returns <tt>true</tt> if this set contained
     * the element (or equivalently, if this set changed as a result of the
     * call). (This set will not contain the element once the call returns.)
     *
     * @param o object to be removed from this set, if present
     * @return <tt>true</tt> if the set contained the specified element
     */
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }

    /**
     * Removes all of the elements from this set. The set will be empty after
     * this call returns.
     */
    public void clear() {
        map.clear();
    }

}  

ConcurrentHashSet继承了AbstractSet,实现了Set、Serializable接口;它底层使用ConcurrentHashMap来实现,其value固定为PRESENT。

其他线程安全的Set 有哪些?

1、java1.6已经帮我们实现了

 Set<String> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap(16));  

2、Collections 的synchronizedSet方法

  static  <T> Set<T> synchronizedSet(Set<T> s, Object mutex) {
    return new SynchronizedSet<>(s, mutex);
}  

3、使用 CopyOnWriteArraySet

4、谷歌的guava其实已经实现了线程安全的ConcurrentHashSet

 Set<String> s = Sets.newConcurrentHashSet();
public static <E> Set<E> newConcurrentHashSet() {
    return newSetFromMap(new ConcurrentHashMap<E, Boolean>());
}

static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
    return Collections.newSetFromMap(map);
}  

文章来源:智云一二三科技

文章标题:Javaset集合的线程安全类你知道吗?ConcurrentHashSet了解一下

文章地址:https://www.zhihuclub.com/200331.shtml

关于作者: 智云科技

热门文章

网站地图