Java基础之线程安全的集合、原子更新、并发的批操作、写数组拷贝、Arrays的并行、Collections将集合转换为安全集合

随笔,笔记

Java为我们提供了 线程安全的集合,如ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue,在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();

写数组的拷贝

  • CopyOnWriteArrayListCopyOnWriteArraySet 线程安全的集合,所有的修改线程对底层数组进行复制。
  • 当 集合上 进行迭代的线程数超过修改线程数 时适用。

当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是集合的数组已经被替换了。因而 旧的迭代器拥有一致的视图,访问无需任何同步的开销。(就是不同步呗)

并行数组算法

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

如果 迭代过程中,别的线程 修改 集合,会报出异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值