java并发编程之线程进阶
- 说明:
- 线程的三大特性
- 原子性
- 可见性
- 有序性
- java.util.concurrent包常见使用类
- 原子操作类
- 并发容器
- ConcurrentHashMap
- ConcurrentLinkedQueue
- ConcurrentSkipListMap
- ConcurrentSkipListSet
- 并发框架
- ThreadPoolExecutor
- Executors
- ComCompletionService
- ForkJoinPool
- CompletableFuture
- runAsync 和 supplyAsync方法
- 计算结果完成时的回调方法
- thenApply 方法
- handle 方法
- thenAccept 消费处理结果
- thenRun 方法
- thenCombine 合并任务
- thenAcceptBoth
- applyToEither 方法
- runAfterEither 方法
- runAfterBoth
- thenCompose 方法
- 并发工具类
- CountDownLatch
- Semaphore
- CyclicBarrier
- 思考join()
说明:
线程间对资源的争夺产生了多线程问题,并发问题,对两个及两个以上资源的争夺可能产生死锁问题,那么如何有效解决呢?请继续我们的学习!
线程的三大特性
原子性
是指一个操作是不可中断,不可分割的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。比如 a=0;(a非long和double类型)这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++;这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性,其他技术也可以保证如atomic包,cas算法,synchronize,lock
可见性
是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行来说,可见性问题是不存在的。
解释:线程工作时有各本地内存,本地内存数据来自于主内存,每次修改都先改自己本地内存,再写会主内存,这两个操作期间其他线程对线程修改是无感知的,执行有主内存更新后才知道,我们可以用sychronized,volatile保证其可见性,这样修改后就立马刷新主内存从而其他线程需要重新从主内存获取
有序性
ava内存模型具有先天的有序性,不通过任何手段,就能得到保证的有序性,称为happens-before原则。如果两个操作的执行顺序无法从本原则推测出来,虚拟机将可随意地对其进行重排序。
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
保证有序性:Java提供了volatile ,synchronized ,Lock可以保证有序性。java内存模型一个列出了八种Happens-before规则,如果两个操作的次序不能从这八种规则中推倒出来,则即使是用了关键字也不能不能保证有序性。
java.util.concurrent包常见使用类
原子操作类
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
(1)AtomicBoolean: 原子更新布尔类型。
(2)AtomicInteger: 原子更新整型。
(3)AtomicLong: 原子更新长整型。
以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下:
(1)int addAndGet(int delta): 以原子的方式将输入的数值与实例中的值相加,并返回结果。
(2)boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值。
(3)int getAndIncrement(): 以原子的方式将当前值加1,注意,这里返回的是自增前的值。
(4)void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
(5)int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值。
实例:
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.getAndIncrement());
###########################################
通过原子的方式更新数组里的某个元素,Atomic包提供了一下4个类:
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。
上述几个类提供的方法几乎一样,我们以AtomicIntegerArray类为例讲解,其常用方法如下:
int addAndGet(int i , int delta):以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i , int expect , int update):如果当前值等于预期值,则以原子方式将数组位置
实例:
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0,3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
##############################
原子更新基本类型的AtomicInterger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类:
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)。
public static AtomicReference<User> atomicUserRef = new
AtomicReference<User>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicUserRef.set(user);
User updateUser = new User("Shinichi", 17);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
##########################
如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新。
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段(属性)必须使用public volatile修饰符。
// 创建原子更新器,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 设置柯南的年龄是10岁
User conan = new User("conan", 10);
// 柯南长了一岁,但是仍然会输出旧的年龄
System.out.println(a.getAndIncrement(conan));
// 输出柯南现在的年龄
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
并发容器
ConcurrentHashMap
在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,
基于以上两个原因,便有了ConcurrentHashMap的登场机会。
(1)线程不安全的HashMap
在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%。原因是
表扩容的时候,容易形成环链
(2)效率低下的HashTable
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率
非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,
也不能使用get方法来获取元素,所以竞争越激烈效率越低。
(3)ConcurrentHashMap的锁分段技术可有效提升并发访问率
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都
必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程
访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率
,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,
然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据
也能被其他线程访问。
ConcurrentLinkedQueue
ConcurrentLinkedQueue使用链表作为数据结构,它采用无锁操作,可以任务是高并发环境
下性能最好的队列。
ConcurrentLinkedQueue是非阻塞线程安全队列,无界,故不太适合做生产者消费者模式,
而LinkedBlockingQueue是阻塞线程安全队列,可以做到有界,通常用于生产者消费者模式。
ConcurrentSkipListMap
SkipList(跳表)是一种随机性的数据结构,用于替代红黑树,因为它在高并发的情况下,
性能优于红黑树。跳表实际上是以空间换取时间;线程安全的有序Map。使用跳表实现高效并发。
ConcurrentSkipListSet
它是一个有序的、线程安全的Set,相当于线程安全的TreeSet。
并发框架
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池核心线程数(平时保留的线程数)
maximumPoolSize:线程池最大线程数(当workQueue都放不下时,启动新线程,最大线程数)
keepAliveTime:超出corePoolSize数量的线程的保留时间。
unit:keepAliveTime单位
workQueue:阻塞队列,存放来不及执行的线程
ArrayBlockingQueue:构造函数一定要传大小
LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务 时,容易造成 内存耗尽。
SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
PriorityBlockingQueue : 优先队列
threadFactory:线程工厂
handler:饱和策略
AbortPolicy(默认):直接抛弃
CallerRunsPolicy:用调用者的线程执行任务
DiscardOldestPolicy:抛弃队列中最久的任务
DiscardPolicy:抛弃当前任务
实例:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 7, 10,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
ExecutorCompletionService<String> executorCompletionService = new
ExecutorCompletionService(threadPoolExecutor);
executorCompletionService.submit(testCallable);
Future<String> take = executorCompletionService.take();
String taskName = take.get();
Executors
Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
1、public static ExecutorService newFiexedThreadPool(int Threads) 创建固定数目线程的线程池。
2、public static ExecutorService newCachedThreadPool():创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
3、public static ExecutorService newSingleThreadExecutor():创建一个单线程化的Executor。
4、public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。
Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。
Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。可以通过 《Java Concurrency in Practice》 一书了解更多关于关闭线程池和如何处理 pending 的任务的知识。
Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。
实例:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class CallableDemo{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();
//创建10个任务并执行
for (int i = 0; i < 10; i++){
//使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
Future<String> future = executorService.submit(new TaskWithResult(i));
//将任务执行结果存储到List中
resultList.add(future);
}
//遍历任务的结果
for (Future<String> fs : resultList){
try{
while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成
System.out.println(fs.get()); //打印各个线程(任务)执行的结果
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}finally{
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
executorService.shutdown();
}
}
}
}
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
/**
* 任务的具体过程,一旦任务传给ExecutorService的submit方法,
* 则该方法自动在一个线程上执行
*/
public String call() throws Exception {
System.out.println("call()方法被自动调用!!! " + Thread.currentThread().getName());
//该返回结果将被Future的get方法得到
return "call()方法被自动调用,任务返回的结果是:" + id + " " + Thread.currentThread().getName();
}
}
ComCompletionService
ComCompletionService包装Executor对象,并且内置一个已完成队列。还有调度逻辑。
会将已完成的任务放到BlockQueue中,要用的时候直接take,获取到最先完成的任务
实例:
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
for (int j = 1; j <= 5; j++) {
final int index = j;
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//第三个线程睡眠等待
if (index == 3) {
java.lang.Thread.sleep(3000l);
}
return index;
}
});
}
threadPool.shutdown();
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程:"+completionService.take().get()+" 任务执行结束:");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
ForkJoinPool
ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,
把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。
ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask<T> task) 或invoke(ForkJoinTask<T> task)方法来执行指定任务了。
其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。
实例:
public class ForkJoinPoolAction {
public static void main(String[] args) throws Exception{
PrintTask task = new PrintTask(0, 300);
//创建实例,并执行分割任务
ForkJoinPool pool = new ForkJoinPool();
pool.submit(task);
//线程阻塞,等待所有任务完成
pool.awaitTermination(2, TimeUnit.SECONDS);
pool.shutdown();
}
}
* Function: 继承RecursiveAction来实现“可分解”的任务。
class PrintTask extends RecursiveAction{
private static final int THRESHOLD = 50; //最多只能打印50个数
private int start;
private int end;
public PrintTask(int start, int end) {
super();
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if(end - start < THRESHOLD){
for(int i=start;i<end;i++){
System.out.println(Thread.currentThread().getName()+"的i值:"+i);
}
}else {
int middle =(start+end)/2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
//并行执行两个“小任务”
left.fork();
right.fork();
}
}
}
实例2:
/**
* File Name:ForJoinPollTask.java
*/
package work1201.basic;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
/**
* ClassName:ForJoinPollTask <br/>
* Function: 对一个长度为100的元素值进行累加
*/
public class ForJoinPollTask {
public static void main(String[] args) throws Exception {
int[] arr = new int[100];
Random random = new Random();
int total =0;
//初始化100个数组元素
for(int i=0,len = arr.length;i<len;i++){
int temp = random.nextInt(20);
//对数组元素赋值,并将数组元素的值添加到sum总和中
total += (arr[i]=temp);
}
System.out.println("初始化数组总和:"+total);
SumTask task = new SumTask(arr, 0, arr.length);
// 创建一个通用池,这个是jdk1.8提供的功能
ForkJoinPool pool = ForkJoinPool.commonPool();
Future<Integer> future = pool.submit(task); //提交分解的SumTask 任务
System.out.println("多线程执行结果:"+future.get());
pool.shutdown(); //关闭线程池
}
}
/**
* Function: 继承抽象类RecursiveTask,通过返回的结果,来实现数组的多线程分段累累加
* RecursiveTask 具有返回值
*/
class SumTask extends RecursiveTask<Integer>{
private static final int THRESHOLD = 20; //每个小任务 最多只累加20个数
private int arry[];
private int start;
private int end;
/**
* Creates a new instance of SumTask.
* 累加从start到end的arry数组
* @param arry
* @param start
* @param end
*/
public SumTask(int[] arry, int start, int end) {
super();
this.arry = arry;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum =0;
//当end与start之间的差小于threshold时,开始进行实际的累加
if(end - start <THRESHOLD){
for(int i= start;i<end;i++){
sum += arry[i];
}
return sum;
}else {//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
int middle = (start+ end)/2;
SumTask left = new SumTask(arry, start, middle);
SumTask right = new SumTask(arry, middle, end);
//并行执行两个 小任务
left.fork();
right.fork();
//把两个小任务累加的结果合并起来
return left.join()+right.join();
}
}
}
CompletableFuture
这个类是Future的实现,它能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中,而这是通过允许CF实例与回调方法进行组合形成新的CF来实现的。
runAsync 和 supplyAsync方法
CompletableFuture 提供了四个静态方法来创建一个异步操作。
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。
•runAsync方法不支持返回值。
•supplyAsync可以支持返回值。
示例
//无返回值
public static void runAsync() throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
System.out.println("run end ...");
});
future.get();
}
//有返回值
public static void supplyAsync() throws Exception {
CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
System.out.println("run end ...");
return System.currentTimeMillis();
});
long time = future.get();
System.out.println("time = "+time);
}
计算结果完成时的回调方法
当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。
whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
示例
public static void whenComplete() throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
if(new Random().nextInt()%2>=0) {
int i = 12/0;
}
System.out.println("run end ...");
});
future.whenComplete(new BiConsumer<Void, Throwable>() {
@Override
public void accept(Void t, Throwable action) {
System.out.println("执行完成!");
}
});
future.exceptionally(new Function<Throwable, Void>() {
@Override
public Void apply(Throwable t) {
System.out.println("执行失败!"+t.getMessage());
return null;
}
});
TimeUnit.SECONDS.sleep(2);
}
thenApply 方法
当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
private static void thenApply() throws Exception {
CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
@Override
public Long get() {
long result = new Random().nextInt(100);
System.out.println("result1="+result);
return result;
}
}).thenApply(new Function<Long, Long>() {
@Override
public Long apply(Long t) {
long result = t*5;
System.out.println("result2="+result);
return result;
}
});
long result = future.get();
System.out.println(result);
}
第二个任务依赖第一个任务的结果。
handle 方法
handle 是执行任务完成时对结果的处理。
handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
示例
public static void handle() throws Exception{
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int i= 10/0;
return new Random().nextInt(10);
}
}).handle(new BiFunction<Integer, Throwable, Integer>() {
@Override
public Integer apply(Integer param, Throwable throwable) {
int result = -1;
if(throwable==null){
result = param * 2;
}else{
System.out.println(throwable.getMessage());
}
return result;
}
});
System.out.println(future.get());
}
从示例中可以看出,在 handle 中可以根据任务是否有异常来进行做相应的后续处理操作。
而 thenApply 方法,如果上个任务出现错误,则不会执行 thenApply 方法。
thenAccept 消费处理结果
接收任务的处理结果,并消费处理,无返回结果。
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
示例
public static void thenAccept() throws Exception{
CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt(10);
}
}).thenAccept(integer -> {
System.out.println(integer);
});
future.get();
}
从示例代码中可以看出,该方法只是消费执行完成的任务,并可以根据上面的任务返回的结果进行处理。并没有后续的输错操作。
thenRun 方法
跟 thenAccept 方法不一样的是,不关心任务的处理结果。只要上面的任务执行完成,就开始执行 thenAccept 。
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
示例
public static void thenRun() throws Exception{
CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt(10);
}
}).thenRun(() -> {
System.out.println("thenRun ...");
});
future.get();
}
该方法同 thenAccept 方法类似。不同的是上个任务处理完成后,并不会把计算的结果传给 thenRun 方法。只是处理玩任务后,执行 thenAccept 的后续操作。
thenCombine 合并任务
thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
示例
private static void thenCombine() throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "hello";
}
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "hello";
}
});
CompletableFuture<String> result = future1.thenCombine(future2, new BiFunction<String, String, String>() {
@Override
public String apply(String t, String u) {
return t+" "+u;
}
});
System.out.println(result.get());
}
thenAcceptBoth
当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行消耗
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);
示例
private static void thenAcceptBoth() throws Exception {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1="+t);
return t;
}
});
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f2="+t);
return t;
}
});
f1.thenAcceptBoth(f2, new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer t, Integer u) {
System.out.println("f1="+t+";f2="+u+";");
}
});
}
applyToEither 方法
两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作。
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
示例
private static void applyToEither() throws Exception {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1="+t);
return t;
}
});
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f2="+t);
return t;
}
});
CompletableFuture<Integer> result = f1.applyToEither(f2, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
System.out.println(t);
return t * 2;
}
});
System.out.println(result.get());
}
runAfterEither 方法
两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
示例
private static void runAfterEither() throws Exception {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1="+t);
return t;
}
});
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f2="+t);
return t;
}
});
f1.runAfterEither(f2, new Runnable() {
@Override
public void run() {
System.out.println("上面有一个已经完成了。");
}
});
}
runAfterBoth
两个CompletionStage,都完成了计算才会执行下一步的操作(Runnable)
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);
示例
private static void runAfterBoth() throws Exception {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f1="+t);
return t;
}
});
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
try {
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f2="+t);
return t;
}
});
f1.runAfterBoth(f2, new Runnable() {
@Override
public void run() {
System.out.println("上面两个任务都执行完成了。");
}
});
}
thenCompose 方法
thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;
示例
private static void thenCompose() throws Exception {
CompletableFuture<Integer> f = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = new Random().nextInt(3);
System.out.println("t1="+t);
return t;
}
}).thenCompose(new Function<Integer, CompletionStage<Integer>>() {
@Override
public CompletionStage<Integer> apply(Integer param) {
return CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int t = param *2;
System.out.println("t2="+t);
return t;
}
});
}
});
System.out.println("thenCompose result : "+f.get());
}
并发工具类
CountDownLatch
CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。
public class Test {
private final static int threadCount = 200;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
} catch (Exception e) {
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
Thread.sleep(100);
System.out.println(threadNum);
Thread.sleep(100);
}
}
Semaphore
Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock与 unlock过程。相对来说他也有两个主要的方法:
用于获取权限的acquire(),其底层实现与CountDownLatch.countdown()类似;
用于释放权限的release(),其底层实现与acquire()是一个互逆的过程。
public class Test {
private final static int threadCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
// 每次最多三个线程获取许可
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
// 获取一个许可
semaphore.acquire();
// semaphore.acquire(3); // 获取多个许可
test(threadNum);
// 释放一个许可
semaphore.release();
//semaphore.acquire(3); // 获取多个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
CyclicBarrier
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
public class Test {
private static CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await();
log.info("{} continue", threadNum);
}
}
思考join()
join()方法的作用,是等待这个线程结束;
谁调用谁先执行