大家好,我是北军。线程安全的问题真是老生常谈了。这几天看到一个HashSet线程安全的操作,在这里分享给大家。在本文中,我们将分享几种构造线程安全的HashSet的方法。使用ConcurrentHashMap工厂方法构造线程安全的HashSet首先我们来看_ConcurrentHashMap_暴露的静态方法--newKeySet()。该方法返回一个Set实例,相当于实现了java.util.Set接口,可以使用Set的一些常用操作,如add()、contains()等。例如:SetthreadSafeUniqueNumbers=ConcurrentHashMap.newKeySet();threadSafeUniqueNumbers.add(23);threadSafeUniqueNumbers.add(45);这里返回的Set其实有点类似于HashSet,因为两者都是基于Hash算法实现的,线程同步逻辑带来的额外开销也很小,因为它归根结底是ConcurrentHashMap的一部分。不过这个只能用在Java8以上的版本,我想大部分公司应该至少有Java8,直接用就可以了。现在,我们了解到可以使用ConcurrentHashMap#newKeySet()来构建一个线程安全的HashSet,实际上在ConcurrentHashMap中定义为KeySetView。ConcurrentHashMap其实有两个实例方法可以用来构建KeySetView,一个是keySet(),一个是keySet(defaultValue)。两种方法都可以创建KeySetView的实例,KeySetView和Map是一种连接关系。每次我们在Map中添加一个新的键值对时,Set中的数据也会随之添加。让我们举几个例子来看看这两种方法之间的区别。KeySet()方法keySet()方法和keySet(defaultValue)方法最大的区别是不能直接向Set中添加数据。如果直接添加,会抛出UnsupportedOperationException,源码中的定义如下。publicKeySetViewkeySet(){KeySetViewks;如果((ks=keySet)!=null)返回ks;returnkeySet=newKeySetView(this,null);}//addpublicbooleanadd(Ke){Vv;if((v=value)==null)thrownewUnsupportedOperationException();returnmap.putVal(e,v,true)==null;}所以我们只能按如下方式使用。ConcurrentHashMapnumbersMap=newConcurrentHashMap<>();SetnumbersSet=numbersMap.keySet();numbersMap.put(1,"一");numbersMap.put(2,"二");numbersMap.put(3,"Three");System.out.println("删除前映射:"+numbersMap);System.out.println("删除前设置:"+numbersSet);numbersSet.remove(2);System.out.println("移除后设置:"+numbersSet);System.out.println("删除后的地图:"+numbersMap);输出结果如下。移除前映射:{1=One,2=Two,3=Three}移除前设置:[1,2,3]移除后设置:[1,3]移除后映射:{1=One,3=Three}KeySet(defaultValue)方法keySet(defaultValue),由于设置了默认值,所以添加时不会报错。JDK源码定义如下:publicKeySetViewkeySet(VmappedValue){if(mappedValue==null)thrownewNullPointerException();returnnewKeySetView(this,mappedValue);}所以我们可以按如下方式使用它。ConcurrentHashMapnumbersMap=newConcurrentHashMap<>();SetnumbersSet=numbersMap.keySet("SET-ENTRY");numbersMap.put(1,"One");numbersMap.put(2,"Two");numbersMap.put(3,"Three");System.out.println("Mapbeforeadd:"+numbersMap);System.out.println("Setbeforeadd:"+numbersSet);numbersSet.addAll(作为列表(4,5));System.out.println("添加后的地图:"+numbersMap);System.out.println("添加后设置:"+numbersSet);输出结果如下:Mapbeforeadd:{1=One,2=Two,3=Three}Setbeforeadd:[1,2,3]Mapafteradd:{1=One,2=Two,3=三、4=SET-ENTRY,5=SET-ENTRY}setafteradd:[1,2,3,4,5]使用Collections创建线程安全的Setjava.util.Collections有一个线程同步方法可以用于创建,示例代码如下。SetsyncNumbers=Collections.synchronizedSet(newHashSet<>());syncNumbers.add(1);该方法的性能不如ConcurrentHashMap高效。由于使用了同步锁,增加了一些额外的开销。使用CopyOnWriteArraySet构建线程安全的Set使用CopyOnWriteArraySet创建线程安全的Set也非常简单。示例代码如下:SetcopyOnArraySet=newCopyOnWriteArraySet<>();copyOnArraySet.add(1);从性能的角度来看,这种方法并不理想。CopyOnWriteArraySet背后的实现是CopyOnWriteArrayList,它最终使用数组来存储Data,即contains()或remove()操作,复杂度为O(n),而使用HashMap复杂度为O(1)。使用此实现时,建议集合大小通常保持较小,只读操作占主导地位。总结在本文中,我们看到了创建线程安全Set的不同方法,并比较了它们之间的差异。所以以后使用的时候可以考虑使用ConcurrentHashMap创建的Set。