接上篇 juc–并发编程的核心问题总结②
我们平时最常见的是同步回调,同步回调是会阻塞的,单个的线程需要等待结果的返回才能继续执行。
假设有两个任务A和B,A任务中需要使用B任务计算成果,有两种方法实现:
A和B在同一个线程中顺序执行。即先执行B,得到返回结果之后再执行A。
A和B并行执行。当A需要B的计算结果时如果B还没有执行完,A可以先做其他的工作,避免阻塞,过一段时间后再询问一次B。
我们可以直接在A中写一个方法对B处理完的结果进行处理,然后B处理完之后调用A这个方法。
通过Future接口实现
Future类存在于JDK的concurrent包中,主要用途是接收Java的异步线程计算返回的结果。
java.util.concurrent.Future
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 发送请求 Void:没有返回结果
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
try {
// 模仿耗时的操作
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync-》Void");
});
System.out.println(Thread.currentThread().getName()+"线程");
}
}
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 发送请求 Void:没有返回结果
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
try {
// 模仿耗时的操作
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync-》Void");
});
System.out.println(Thread.currentThread().getName()+"线程");
//获取阻塞执行结果
Void result = future.get();
}
}
// 有返回值的异步回调 supplyAsync
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"runAsync-》Integer");
//成功则返回1024
return 1024;
});
System.out.println(future.whenComplete((t, u) -> {
System.out.println("t:" + t + " u:" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
//失败则返回233
return 233;
}).get());
// 有返回值的异步回调 supplyAsync
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"runAsync-》Integer");
int i = 2/0;
//成功则返回1024
return 1024;
});
System.out.println(future.whenComplete((t, u) -> {
System.out.println("t:" + t + " u:" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
//失败则返回233
return 233;
}).get());
}
JMM-----Java内存模型
在java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。
局部变量、方法定义参数、异常处理器参数不会在线程间共享,它们不会有内存可见性的问题,也不收内存模型的影响。
JMM定义了线程和主内存间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存存储了该线程读/写共享变量的副本。本地内存是JMM的一个抽象概念并不实际存在。
JMM的约定:
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
JMM对这八种指令的使用,制定了如下规则:
若线程A修改了共享变量的值,但线程B的本地内存中该变量仍然是旧值,即线程B不能及时可见。怎样让B知道主内存中值发生了变化呢?使用Volatile。
Volatile是java虚拟机提供的轻量级的同步机制。
详见 深入理解java中volatile的特性
什么是CAS?
CAS是Compare and Swap的缩写,就是比较并替换。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?
答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(999);
//compareAndSet(int expect, int update)
System.out.println(atomicInteger.compareAndSet(999, 666));//true
System.out.println(atomicInteger.get());//666
}
}
ABA问题:
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然为A值,那就能说明它的值没有被改变过吗?不能,因为在这段时间里它的值可能被修改成了一个值B,后来又被修改回来了A,那CAS就会误以为它从来就没有变过。
JUC包为了解决这个问题,提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。不过目前这个类比较鸡肋。
CAS的缺点: