随笔,笔记
Java为我们提供了 线程安全的集合,如ConcurrentHashMap
,ConcurrentSkipListMap
,ConcurrentSkipListSet
,ConcurrentLinkedQueue
,在java.util.concurrent
包里,允许并发地访问数据结构的不同部分来使冲突 最小。
使用 遍历来求 集合的大小,当数量庞大时,需要使用 mappingCount() 方法,转换为 long 返回。
集合返回的是弱一致性的迭代器:
迭代器不能够 确切保证,反映出构造时候的所有修改。但是,它不会 将一个值返回两次,也不抛出异常。
为啥 并发散列映射将桶组织成树?
散列映射将有相同散列码的所有条目放到同一个桶中。当使用 散列函数不当,所有条目会被放到很少的桶中,造成性能下降。组织成树,就可以保证 性能为 O(logn)
。
映射条目的原子更新:
原本线程安全的数据结构,也可能有非线程安全的操作。
ConcurrentHashMap<String,Long> map = new ConcurrentHashMap<>();
Long oldValue = map.get("key");
Long newValue = oldValue == null ? 1 : oldValue+1;
map.put("key",newValue);
- 由于操作序列不是原子的,所以结果不可预测。
Java 8提供了 compute
方法,让我们更加方便的完成原子更新,传入一个键和一个计算新值的函数。
map.compute("key",(k,v)-> v==null?1:v+1);
- 还有一个 merge 方法,在键不存在时,使用初始值。
map.compute("key",(k,v)-> v==null?1:v+1);
// 就不用 判断 null
map.merge("key",1L,(existingValue,newValue)-> existingValue+newValue);
这个函数运行 可能会阻塞对映射的其他更新。
并发散列映射的批操作:
批操作,即使有其他线程在处理映射,这些操作也能安全地执行。提供了三种不同的操作:
- 搜索,为每个键或值提供一个函数,直到函数生成一个 非null 的结果,然后搜索终止。
- 归约,组合所有的键或值,需要提供一个累加函数。
- forEach 为所有键或值提供一个函数。
每个操作对应的方法,命名规则:
- operKeys:处理键。
- operValues:处理值。
- oper:处理键和值。
- operEntries:处理 Map.Entry 对象。
还需要指定一个阈值,如果映射包含的元素大于这个阈值,就会并行完成批操作。
// 100L 为 指定阈值
map.searchValues(100L,(v)-> v> 100?v:null);
map.reduceValues(100L,Long::sum);
// 把输入类型 转换为 基本类型 ToLong
// 还有对应的
// ToInt
// ToDouble
map.reduceValuesToLong(100L,Long::longValue,0,Long::sum);
并发集视图
由于 没有 ConcurrentHashSet
,于是 利用ConcurrentHashMap
的 Key 来 代表 Set 集合,Key 对应的 Value 是一个假值。
Set<String> set = ConcurrentHashMap.<String>newKeySet();
// newKeySet 方法的实现源码
public static <K> KeySetView<K,Boolean> newKeySet() {
return new KeySetView<K,Boolean>
(new ConcurrentHashMap<K,Boolean>(), Boolean.TRUE);
}
如果 已经有 映射集合
Set<String> set = map.keySet();
写数组的拷贝
CopyOnWriteArrayList
和CopyOnWriteArraySet
线程安全的集合,所有的修改线程对底层数组进行复制。- 当 集合上 进行迭代的线程数超过修改线程数 时适用。
当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是集合的数组已经被替换了。因而 旧的迭代器拥有一致的视图,访问无需任何同步的开销。(就是不同步呗)
并行数组算法
Arrays
类提供了大量并行化操作。
int [] a = {1,3,2};
Arrays.parallelSort(a);
parallelSort()
方法可以对一个基本类型或对象的数组进行排序。- 也可以对 指定数组一个范围,进行排序
int [] a = {1,3,2};
Arrays.parallelSetAll(a,i->i%10);
parallelSetAll()
:用 Lambda 计算的值,来填充一个数组。
int [] a = {1,3,2};
Arrays.parallelPrefix(a,(x,y)->x*y);
parallelPrefix()
在前一个 计算的结果上,进行计算。[1, 1X2, 1X2X3]
将线程不安全的集合变成线程安全
通过Collections
类的同步包装器,将不安全集合 转换成 安全的集合。
HashMap<String,String> hashMap = new HashMap<>();
Map<String,String> synHashMap = Collections.synchronizedMap(hashMap);
使用锁加以保护,提供了线程安全。
应确保没有任何线程 通过 原始的非同步方法 访问数据结构。一般 确保不存在指向原始对象的引用,简单构造一个集合,然后立即传递给包装器。
Map<String,String> synHashMap = Collections.synchronizedMap(new HashMap<>());
如果 迭代过程中,别的线程 修改 集合,会报出异常。