文档章节

4、并发增强

MiaoXG
 MiaoXG
发布于 2016/06/22 10:36
字数 1321
阅读 86
收藏 0

4.1 概述

这部分用的不多,仅简单记录一下新特性

4.2 原子值

4.2.1 更新方法

Java 8 增加了两个新方法 updateAndGet 和 accumulateAndGet 用于更新原子类的值,取代老API的循环方式。

AtomicInteger ai = new AtomicInteger();

// 多线程环境下安全更新
Integer i = ai.updateAndGet(x -> 5);
System.out.println(i);   // 输出 5

// 多线程环境下安全更新  将原子值和传入的参数组合
ai.accumulateAndGet(1, (oldValue, paramValue) -> oldValue + paramValue);
System.out.println(ai.get());   // 输出 6

4.2.2 LongAdder

如果程序内有高度的竞争,大量的线程访问同一个原子值,可以使用 LongAdder 和 LongAccumulator,这个类是 Java 8 提供用于在高度竞争环境下替代 AtomicLong 的。

LongAdder adder = new LongAdder();
adder.add(2);
System.out.println(adder.intValue());   // 输出 2

adder.increment();
System.out.println(adder.sum());  // 输出 3  sum方法返回long型

// 同 accumulateAndGet 方法, 将原子值和传入的参数组合
LongAccumulator la = new LongAccumulator((left, right) -> left + right, 10);
System.out.println(la.intValue());  // 输出 10

4.3 ConcurrentHashMap

额外提一点,在 1.5 版本, ConcurrentHashMap 存在死锁的可能(具体源码就不再分析了),1.6 版本以后修复了这个问题。

4.3.1 更新值

一个误区: 对关键字计数

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
Integer oldValue = map.get("key");
Integer newValue = oldValue == null ? 1 : oldValue + 1;
map.put("key", newValue);

这段代码并不是线程安全的,有可能发生多个线程更新的值相等。因为 ConcurrentHashMap 的设计目的是保护内部结构不被破坏,任何 get 、put 操作不会导致内部链表节点丢失或形成回路,而不是维护操作顺序的原子性。

正确的更新方法,CAS方式循环更新:

do {
    oldValue = map.get("key");
    newValue = oldValue == null ? 1 : oldValue + 1;
} while (!map.replace("key", oldValue, newValue));

Java 8 中也可以这样

ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>();
map.putIfAbsent("key", new LongAdder()).increment();

4.3.2 批量数据操作

Java 8 为 ConcurrentHashMap 提供了批量数据操作, 即使其它线程同时操作时也可以安全的执行。 批量数据操作有三类:

  • search:对所有的键和(或)值应用一个函数,直到函数返回一个非 null 的结果。
  • reduce:通过提供的聚合函数,将所有的键和(或)值组合起来。
  • forEach:对所有的键和(或)值应用一个函数,注意这个与 Map 中的 forEach 方法不一样。

每类操作都有对键、值、键和值、Map.Entry对象操作的4个版本,列举一下。

  • searchKeys / reduceKeys / forEachKey
  • searchValues / reduceValues / forEachValues
  • search / reduce / forEach
  • searchEntries / reduceEntries / forEachEntry

在使用这几个操作的时候,需要指定一个 并行阀值。如果 map.size 大于阀值,批量操作就以并行的方式执行。如果不想并行执行,可以使用 Long.MAX_VALUE作为阀值;如果想尽可能的多线程执行,可以用1作为阀值。

search

很简单,看 一眼 API 就能明白,以一个方法api为例: U search(long threshold, BiFunction<? super K, ? super V, ? extends U> f)

forEach

forEach 方法有两种形式,第一种就是我们常用的,参数为消费者,只不过这里参数多了一个阀值,

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.forEach(3, (k, v) -> System.out.println(k +"-" + v));

第二中又多了一个参数,是一个转换其函数,转换后的结果传递给消费者函数,如果转换后的结果为 null,值会被跳过。

map.forEach(3,
        (k, v) -> k +"-"+v,       // 转换器
        System.out::print);       // 消费者

reduce

用法见前面章节介绍,这里也是多了一个阀值参数。

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
long threshold = 3;
map.reduceValues(threshold, (s, s2) -> s + s2);

同 forEach 一样,你也可以提供一个转换器函数。

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
long threshold = 3;
map.reduceValues(threshold,
        v -> v == null ? null: v + " ",  // 转换器
        (s, s2) -> s + s2);              // 聚合方法

4.4 并行数组

4.4.1 sort

可能是因为设计者想更明确的表达并行排序,所以并没有修改原 sort方法,而是新增加了 parallelSort 和 parallel* 等方法。最简单的并行排序如下,跟 sort 没什么区别。

String[] arr = {"an", "ba", "daf", "ads", "ca"};
Arrays.parallelSort(arr);

4.4.2 parallelSetAll

这个比较有意思,它的参数是一个位置索引,所以能构造跟位置有关的数组值,

String[] arr = {"an", "ba", "daf", "ads", "ca"};

Arrays.parallelSetAll(arr, i -> arr[i] + i);

for (String s : arr) {
    System.out.println(s);
}

// 输出结果是:
an0
ba1
daf2
ads3
ca4

4.4.3 parallelPrefix

类似reduce, 对数组内元素进行聚合

String[] arr = {"an", "ba", "daf", "ads", "ca"};

Arrays.parallelPrefix(arr, (x, y) -> x+"+"+y);

for (String s : arr) {
    System.out.println(s);
}
// 输出结果
an
an+ba
an+ba+daf
an+ba+daf+ads
an+ba+daf+ads+ca

4.5 CompletableFuture

相对于老的 Future 增加了类似 Stream API 流水操作的功能,使同一个线程执行的代码能写在一起,虽然他们不是连续执行的。

CompletableFuture<String> contents = CompletableFuture.completedFuture("abcdef");

System.out.println("111");

// 同一线程执行的代码写在一起
CompletableFuture<String> c2 = contents.thenApplyAsync(x -> {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("333");
    return x;
});

c2.thenAcceptAsync(x -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(x);
});

// 其他的业务在 Future 执行之前执行
System.out.println("222");

// 防止测试程序结束
try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

// 输出顺序是:
111
222
333
abcdef

上面的例子为了顺序清晰,加了一些恶心的代码,精简一下应该是这样

CompletableFuture<String> contents = CompletableFuture.completedFuture("abcdef");

// 同一线程执行的代码写在一起
contents.thenApplyAsync(x -> x).thenAcceptAsync(System.out::println);

// 其他的业务在 Future 执行之前执行
System.out.println("222");

CompletableFuture 流式操作还有几个实用的方法,就不写demo了,需要用的时候再查API。

© 著作权归作者所有

上一篇: 5、杂项
MiaoXG
粉丝 2
博文 19
码字总数 17098
作品 0
朝阳
程序员
私信 提问
Erlang 编程语言发布 R13B4 版本

Erlang是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。 该版本改进内容有: 1. 文档使用 xs...

红薯
2010/04/06
751
0
Beetl 2.0.12 发布,java模板引擎

Beetl是新一代的Java模板引擎,目前大小656K,相对于其他模板引擎,具有功能齐全,语法直观,性能超高,开发和维护模板有很好的体验。 2.0.12版本修复Bug: 1 变量为空,错误提示不够友好 (#...

闲大赋
2014/09/01
2.4K
27
C# 高并发通信库 NetProviderFactory 发布更新

.net csharp 高性能大并发 socket 异步、同步通信库,建立独立的发送缓冲池和接收缓冲池解决发送和接收不断分配缓冲区问题。 更新内容: 1、增加协议模块Protocols; 2、修改udp通信和缓冲区b...

bouyeijiang
2017/08/23
1K
0
为什么阿里禁止在 foreach 循环里进行元素的 remove/add 操作

在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体原因,本文就来深入分析一下该规定背后的思考。 1.foreach循环 foreach循环(Foreach loop)是计算机编程语言中的一种...

架构师springboot
05/10
72
2
阿里开源组件 fastjson-1.1.42 发布

Fastjson 1.1.42 发布了,主要改进内容包括: 1. 修复parser在处理循环引用在某些特定场景下的bug; 2. 支持在Bean上通过JSONType配置DisableCircularReferenceDetect/BeanToArray特性; 3....

wenshao
2014/10/11
9.3K
11

没有更多内容

加载失败,请刷新页面

加载更多

Podman 使用指南

> 原文链接:Podman 使用指南 Podman 原来是 CRI-O 项目的一部分,后来被分离成一个单独的项目叫 libpod。Podman 的使用体验和 Docker 类似,不同的是 Podman 没有 daemon。以前使用 Docker...

米开朗基杨
52分钟前
5
0
拯救 项目经理个人时间的5个技巧

优秀的项目经理都有一个共同点,那就是良好的时间管理能力。专业的项目经理会确保他们的时间投入富有成效,尽可能避免时间浪费。 时间管理叫做GTD,即Getting Things Done——“把事情做完”...

Airship
今天
6
0
LNMP环境介绍,Mariadb安装,服务管理,mariadb安装3

LNMP环境介绍 Nginx 处理的请求有两种,分为 静态与动态 图片,js,css,视频,音频,flash 等都是静态请求,这些数据都不是保存在数据库里面的 动态请求一般来说,需要的数据是在数据库里面...

doomcat
今天
2
0
前端技术之:Prisma Demo服务部署过程记录

安装前提条件: 1、已经安装了docker运行环境 2、以下命令执行记录发生在MackBook环境 3、已经安装了PostgreSQL(我使用的是11版本) 4、Node开发运行环境可以正常工作 首先需要通过Node包管...

popgis
今天
7
0
数组和链表

数组 链表 技巧一:掌握链表,想轻松写出正确的链表代码,需要理解指针获引用的含义: 对指针的理解,记住下面的这句话就可以了: 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指...

code-ortaerc
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部