Java并发编程系列-(9) JDK 8/9/10中的并发
目前已经更新完《Java并发编程》,《Docker教程》和《JVM性能优化》,欢迎关注【后端精进之路】,轻松阅读全部文章。
Java并发编程:
- Java并发编程系列-(1) 并发编程基础
- Java并发编程系列-(2) 线程的并发工具类
- Java并发编程系列-(3) 原子操作与CAS
- Java并发编程系列-(4) 显式锁与AQS
- Java并发编程系列-(5) Java并发容器
- Java并发编程系列-(6) Java线程池
- Java并发编程系列-(7) Java线程安全
- Java并发编程系列-(8) JMM和底层实现原理
- Java并发编程系列-(9) JDK 8/9/10中的并发
Docker教程:
JVM性能优化:
9.1 CompletableFuture
CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强。
手动设置完成状态
CompletableFuture和Future一样,可以作为函数调用的契约,当向CompletableFuture请求数据时,如果数据还没有准备好,请求线程就会等待。但是,我们可以手动设置CompletableFuture的完成状态。
下面的例子中,创建了CompletableFuture对象实例进行计算,同时另外一个线程进行等待,接着,模拟等待一段时间之后,设置完成状态为完成,此时等待线程继续执行。
public class CompletableFutureTest {
public static class waitThread implements Runnable {
CompletableFuture<Integer> resultCompletableFuture = null;
public waitThread(CompletableFuture<Integer> resultCompletableFuture) {
super();
this.resultCompletableFuture = resultCompletableFuture;
}
@Override
public void run() {
int myResult = 0;
try {
System.out.println("Waiting for the result...");
myResult = resultCompletableFuture.get();
System.out.println("Result got, it's " + myResult);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<Integer>();
new Thread(new waitThread(future)).start();
// 模拟等待过程
Thread.sleep(2000);
// 设置完成的结果
future.complete(666);
}
}
异步执行任务
CompletableFuture提供了很方便的异步执行接口,
其中supplyAsync方法用于需要返回值的场景;runAsync方法用于没有返回值的场景。注意到,这两个方法中都可以接受Executor参数,可以方便的让任务在指定的线程池中运行。
流式调用
CompletableFuture提供了类似于JDK 8中list的流式操作,下面例子中,首先利用supplyAsync()执行一个异步任务,接着使用流式操作对任务结果进行处理。
注意到在最后面,调用了get方法,用于获取结果,否则由于CompletableFuture异步执行,main函数不会等待计算完成,直接退出,随着主线程的结束,所有的Daemon线程也会退出,从而导致计算方法无法正常完成。
异常处理
CompletableFuture在执行中遇到异常时,同样的可以利用函数式编程的方法来处理异常,CompletableFuture中提供了一个异常处理方法exceptionally():
组合多个CompletableFuture
CompletableFuture中可以组合多个CompletableFuture,主要有如下两种方法:
1. thenCompose方法
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(null, fn);
}
例子如下:
2. thenCombine方法
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(null, other, fn);
}
例子如下:
支持timeout
在JDK9之后的CompletableFuture增加了timeout功能,如果任务在指定时间内没有完成,则直接抛出异常。
9.2 改进的读写锁:StampedLock
StampedLock是JDK 8中引入的新的锁机制,可以认为是读写锁的一个改进版本,读写锁虽然分离了读和写,使得读与读之间可以完全并发,但是读和写之间仍然是冲突的。读锁会阻塞写锁,它使用的仍然是悲观的策略,如果有大量的读线程,可能引起写线程的饥饿。
stampedLock提供了一种乐观的读策略,这种乐观的策略类似与无锁的操作,使得乐观锁完全不会阻塞写线程。
上面的例子中,首先试图尝试乐观获取锁,方法会返回一个类似于时间戳的stamp,然后进行相应的读取操作,当然为了保证没有其他线程修改了x、y的值,需要调用validate方法来进行验证,判断这个stamp在读过程中是否发生了修改。如果没有修改,则直接进行接下来的计算,否则,升级乐观锁为悲观锁,使用readLock获取读锁。如果当前有其他线程已经获取了锁,当前线程可能被挂起。
9.2 更快的原子类:LongAdder
JDK引入了LongAdder,对之前的atomicInteger的性能进行了增强,AtomicLong 的 Add() 是依赖自旋不断的 CAS 去累加一个 Long 值。如果在竞争激烈的情况下,CAS 操作不断的失败,就会有大量的线程不断的自旋尝试 CAS 会造成 CPU 的极大的消耗。
对于同样的一个 add() 操作,上文说到 AtomicLong 只对一个 Long 值进行 CAS 操作。而 LongAdder 是针对 Cell 数组的某个 Cell 进行 CAS 操作 ,把线程的名字的 hash 值,作为 Cell 数组的下标,然后对 Cell[i] 的 long 进行 CAS 操作。简单粗暴的分散了高并发下的竞争压力。
在实际的操作中,LongAdder并不会一开始就动用数组进行处理,而是将所有数据都记录在一个称为base的变量中,如果在多线程的条件下,大家修改base没有冲突,也没有必要扩展成cell数组,但是,一旦base修改发生冲突,就会初始化cell数组,使用新的策略。如果使用cell数组之后,发现在某一个cell上的更新依然存在冲突,那么系统就会尝试创建新的cell,以减少冲突。
AtomicLong可否可以被LongAdder替代?
有了传说中更高效的LongAdder,那AtomicLong可否不使用了呢?当然不是!
答案就在LongAdder的java doc中,从我们翻译的那段可以看出,LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add方法,而AtomicLong还具有cas方法(要使用cas,在不直接使用unsafe之外只能借助AtomicXXX了)
LongAdder有啥用?
从java doc中可以看出,其适用于统计计数的场景,例如计算qps这种场景。在高并发场景下,qps这个值会被多个线程频繁更新的,所以LongAdder很适合。
参考:
- https://www.jianshu.com/p/22d38d5c8c2a
- 《实战Java高并发程序设计》
本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程。
Java并发编程系列-(9) JDK 8/9/10中的并发的更多相关文章
- Java并发编程系列-(5) Java并发容器
5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...
- Java并发编程系列-(4) 显式锁与AQS
4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...
- Java并发编程系列-(3) 原子操作与CAS
3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...
- Java并发编程系列-(2) 线程的并发工具类
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...
- Java并发编程系列-(1) 并发编程基础
1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...
- Java并发编程系列-(6) Java线程池
6. 线程池 6.1 基本概念 在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理.如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数 ...
- Java并发编程系列-(7) Java线程安全
7. 线程安全 7.1 线程安全的定义 如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的. 类的线程安全表现为: 操作的原子性 内存的可见性 不 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- java并发编程系列原理篇--JDK中的通信工具类Semaphore
前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...
随机推荐
- java 简单实现FtpClient
1. 引入喜闻乐见的maven地址 <dependency> <groupId>commons-net</groupId> <artifactId>co ...
- python 嵌套列表
- Oracle dbms_random包的用法
1.dbms_random.value方法 dbms_random是一个可以生成随机数值或者字符串的程序包.这个包有initialize().seed().terminate().value().no ...
- PHP怎么调用其他类的方法
2个PHP,这个PHP中的类调用另一个PHP中的类,如何调用.Java中是import ,php中是什么?还是用其他什么方法? 1.引用类:比如类名为product,则:include('...路径/ ...
- @hdu - 6427@ Problem B. Beads
目录 @description@ @solution@ @accepted code@ @details@ @description@ 有 m 种不同颜色的珠子,颜色分别为 1~m,每一种颜色的珠子有 ...
- xml path 列转行实例
SQL Server2005提供了一个新查询语法——For XML PATH(''),这个语法有什么用呢?想象一下这样一个查询需求:有两个表,班级表A.学生表B,要查询一个班级里有哪些学生?针对这个需 ...
- 通过PdfiumViewer.dll实现pdf直接从流显示(效果不好)
开源的PdfiumViewer.dll并不完整 需要不开源的pdfium.dll配合使用 引用只需添加PdfiumViewer.dll,但pdfium.dll必须跟它放在一个路径下 界面中添加控件: ...
- 漏洞: RHSA-2017:3075: wget security update
该网址有解决方案 http://www.stumblingblock.cn/3102.html
- Spring Boot JPA 懒加载
最近在使用spring jpa 的过程中经常遇到懒加载的错误:"` org.hibernate.LazyInitializationException: could not initiali ...
- Innodb_large_prefix
innodb_large_prefix Prefixes, defined by the length attribute, can be up to 767 bytes long for InnoD ...