文档章节

[高并发Java 十] JDK8对并发的新支持

Hosee
 Hosee
发布于 2016/02/16 22:17
字数 1697
阅读 8367
收藏 20

1. LongAdder

和AtomicLong类似的使用方式,但是性能比AtomicLong更好。

LongAdder与AtomicLong都是使用了原子操作来提高性能。但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能。在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能。

LongAdder原理图:


AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CAS 指令从机器指令级别操作保证并发的原子性。唯一会制约AtomicLong高效的原因是高并发,高并发意味着CAS的失败几率更高, 重试次数更多,越多线程重试,CAS失败几率又越高,变成恶性循环,AtomicLong效率降低。

LongAdder将把一个value拆分成若干cell,把所有cell加起来,就是value。所以对LongAdder进行加减操作,只需要对不同的cell来操作,不同的线程对不同的cell进行CAS操作,CAS的成功率当然高了(试想一下3+2+1=6,一个线程3+1,另一个线程2+1,最后是8,LongAdder没有乘法除法的API

可是在并发数不是很高的情况,拆分成若干的cell,还需要维护cell和求和,效率不如AtomicLong的实现。LongAdder用了巧妙的办法来解决了这个问题。

初始情况,LongAdder与AtomicLong是相同的,只有在CAS失败时,才会将value拆分成cell,每失败一次,都会增加cell的数量,这样在低并发时,同样高效,在高并发时,这种“自适应”的处理方式,达到一定cell数量后,CAS将不会失败,效率大大提高。

LongAdder是一种以空间换时间的策略。

2. CompletableFuture

实现CompletionStage接口(40余个方法),大多数方法多数应用在函数式编程中。并且支持流式调用 

CompletableFuture是Java 8中对Future的增强版 

简单实现:

import java.util.concurrent.CompletableFuture;

public class AskThread implements Runnable {
	CompletableFuture<Integer> re = null;

	public AskThread(CompletableFuture<Integer> re) {
		this.re = re;
	}

	@Override
	public void run() {
		int myRe = 0;
		try {
			myRe = re.get() * re.get();
		} catch (Exception e) {
		}
		System.out.println(myRe);
	}

	public static void main(String[] args) throws InterruptedException {
		final CompletableFuture<Integer> future = new CompletableFuture<Integer>();
		new Thread(new AskThread(future)).start();
		// 模拟长时间的计算过程
		Thread.sleep(1000);
		// 告知完成结果
		future.complete(60);
	}
}
Future最令人诟病的就是要等待,要自己去检查任务是否完成了,在Future中,任务完成的时间是不可控的。而 CompletableFuture的最大改进在于,任务完成的时间也开放了出来。
future.complete(60);
用来设置完成时间。

CompletableFuture的异步执行:

public static Integer calc(Integer para) {
		try {
			// 模拟一个长时间的执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		return para * para;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		final CompletableFuture<Integer> future = CompletableFuture
				.supplyAsync(() -> calc(50));
		System.out.println(future.get());
	}
CompletableFuture的流式调用:

public static Integer calc(Integer para) {
		try {
			// 模拟一个长时间的执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		return para * para;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		CompletableFuture<Void> fu = CompletableFuture
				.supplyAsync(() -> calc(50))
				.thenApply((i) -> Integer.toString(i))
				.thenApply((str) -> "\"" + str + "\"")
				.thenAccept(System.out::println);
		fu.get();
	}

组合多个CompletableFuture:

public static Integer calc(Integer para) {
		return para / 2;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		CompletableFuture<Void> fu = CompletableFuture
				.supplyAsync(() -> calc(50))
				.thenCompose(
						(i) -> CompletableFuture.supplyAsync(() -> calc(i)))
				.thenApply((str) -> "\"" + str + "\"")
				.thenAccept(System.out::println);
		fu.get();
	}
这几个例子更多是侧重Java8的一些新特性,这里就简单举下例子来说明特性,就不深究了。

CompletableFuture跟性能上关系不大,更多的是为了支持函数式编程,在功能上的增强。当然开放了完成时间的设置是一大亮点。

3. StampedLock

在上一篇中刚刚提到了锁分离,而锁分离的重要的实现就是ReadWriteLock。而StampedLock则是ReadWriteLock的一个改进。StampedLock与ReadWriteLock的区别在于,StampedLock认为读不应阻塞写,StampedLock认为当读写互斥的时候,读应该是重读,而不是不让写线程写。这样的设计解决了读多写少时,使用ReadWriteLock会产生写线程饥饿现象。

所以StampedLock是一种偏向于写线程的改进。

StampedLock示例

import java.util.concurrent.locks.StampedLock;

public class Point {
	private double x, y;
	private final StampedLock sl = new StampedLock();

	void move(double deltaX, double deltaY) { // an exclusively locked method
		long stamp = sl.writeLock();
		try {
			x += deltaX;
			y += deltaY;
		} finally {
			sl.unlockWrite(stamp);
		}
	}

	double distanceFromOrigin() { // A read-only method
		long stamp = sl.tryOptimisticRead();
		double currentX = x, currentY = y;
		if (!sl.validate(stamp)) {
			stamp = sl.readLock();
			try {
				currentX = x;
				currentY = y;
			} finally {
				sl.unlockRead(stamp);
			}
		}
		return Math.sqrt(currentX * currentX + currentY * currentY);
	}
}
上述代码模拟了写线程和读线程, StampedLock根据stamp来查看是否互斥,写一次stamp变增加某个值

tryOptimisticRead()
就是刚刚所说的读写不互斥的情况。

每次读线程要读时,会先判断

if (!sl.validate(stamp))
validate中会先查看是否有写线程在写,然后再判断输入的值和当前的 stamp是否相同,即判断是否读线程将读到最新的数据。如果有写线程在写,或者 stamp数值不同,则返回失败。

如果判断失败,当然可以重复的尝试去读,在示例代码中,并没有让其重复尝试读,而采用的是将乐观锁退化成普通的读锁去读,这种情况就是一种悲观的读法。

stamp = sl.readLock();
StampedLock的实现思想:

CLH自旋锁:当锁申请失败时,不会立即将读线程挂起,在锁当中会维护一个等待线程队列,所有申请锁,但是没有成功的线程都记录在这个队列中。每一个节点(一个节点代表一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。当一个线程试图获得锁时,取得当前等待队列的尾部节点作为其前序节点。并使用类似如下代码判断前序节点是否已经成功释放锁

while (pred.locked) {   
}
这个循环就是不断等前面那个结点释放锁,这样的自旋使得当前线程不会被操作系统挂起,从而提高了性能。

当然不会进行无休止的自旋,会在若干次自旋后挂起线程。


系列:

[高并发Java 一] 前言

[高并发Java 二] 多线程基础

[高并发Java 三] Java内存模型和线程安全

[高并发Java 四] 无锁

[高并发Java 五] JDK并发包1

[高并发Java 六] JDK并发包2

[高并发Java 七] 并发设计模式

[高并发Java 八] NIO和AIO

[高并发Java 九] 锁的优化和注意事项

[高并发Java 十] JDK8对并发的新支持



Reference:

1. http://developer.51cto.com/art/201404/436505.htm

© 著作权归作者所有

共有 人打赏支持
Hosee
粉丝 532
博文 132
码字总数 207228
作品 0
杭州
程序员
加载中

评论(2)

xianlai
xianlai
参考:《实战高并发程序设计》。
xianlai
xianlai
参考:《实战高并发程序设计》。
【荐】一篇令人心情愉悦的面试总结

前言 15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中... 最终有幸去...

茶轴的青春
08/02
0
0
一个两年Java工程师的面试总结

前言 16年毕业到现在也近两年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有幸去...

Java高级架构
07/25
0
0
《Java并发编程与高并发解决方案》课程相关手记汇总 - 持续更新

给《Java并发编程与高并发解决方案》课程准备的手记列表,为了方便大家阅读,单独整理成一篇汇总,学习时结合手记效果会更好哦~ 更多手记可点击我的个人首页:http://www.imooc.com/t/598062...

_Jimin_
05/09
0
0
Java 理论与实践: 修复 Java 内存模型,第 2 部分 (VOLATILE, FINA...

活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型(Java Memory Model, JMM)的公开建议。在本系列文章的 第 1 部分,专栏作者 Brian Goetz 主要介绍最初的 JMM 中的几个严重...

刘小兵2014
2011/07/28
0
0
读书笔记之《Java并发编程的艺术》-线程池和Executor的子孙们

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

流量劫持是如何产生的?

流量劫持,这种古老的攻击沉寂了一段时间后,最近又开始闹的沸沸扬扬。众多知名品牌的路由器相继爆出存在安全漏洞,引来国内媒体纷纷报道。只要用户没改默认密码,打开一个网页甚至帖子,路由...

谢思华
20分钟前
0
0
Hadoop Client无法使用maven下载源码

最近在学习hadoop,使用maven的时候想看一下源码的注释,结果IDEA一直提示无法下载 搞得我一度以为maven坏掉了。 但是通过搜索,发现在maven仓库里确实没有源码.... 而2.8.1以及之前的版本是...

Iceberg_XTY
22分钟前
0
0
为什么程序员千万不要重写代码?

你所做的事情,也许暂时看不到成果,但不要灰心或焦虑,你不是没有成长,而是在扎根。 图片来自网络 0 前言 程序员都有一颗工程师的心,所以当他们到一片新的场地想做的第一件事就是,将旧的...

Java小铺
24分钟前
0
0
VUE集成AdminLte

1. 安装需要到插件 npm i admin-lte -Snpm i jquery -Snpm i axios -Snpm i vue-router -S 2. 配置webpack.config.js 2.1 module.exports.module.rules修改字体loader: {test: /\.(p......

Pasenger
今天
0
0
Spring Aop原理之切点表达式解析

在前面的文章(Spring AOP切点表达式详解)中,我们总结了Spring Aop切点表达式的用法,而在上文(Spring Aop原理之Advisor过滤)中我们讲到,切点表达式的解析主要是在PatternParser.parse...

爱宝贝丶
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部