14、ForkJoin
Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。
public class ForkJoinDemo{
public static void main(String[] args) {
// 创建2000个随机数组成的数组:
long[] array = new long[2000];
long expectedSum = 0;
for (int i = 0; i < array.length; i++) {
array[i] = random();
expectedSum += array[i];
}
System.out.println("Expected sum: " + expectedSum);
// fork/join:
SumTask task = new SumTask(array, 0, array.length);
long startTime = System.currentTimeMillis();
Long result = ForkJoinPool.commonPool().invoke(task);
//ForkJoinPool forkJoinPool = new ForkJoinPool();
//ForkJoinTask<Long> submit = forkJoinPool.submit(task);
//Long result = submit.get();
long endTime = System.currentTimeMillis();
System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
static Random random = new Random(0);
static long random() {
return random.nextInt(10000);
}
}
class SumTask extends RecursiveTask<Long> {
static final int THRESHOLD = 500;
long[] array;
int start;
int end;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 如果任务足够小,直接计算:
long sum = 0;
for (int i = start; i < end; i++) {
sum += this.array[i];
}
return sum;
}
// 任务太大,一分为二:
int middle = (start + end)/2;
System.out.println(String.format("from %d~%d ===> %d~%d,%d~%d",start,end,start,middle,middle,end));
SumTask sumTask = new SumTask(array, start, middle);
SumTask sumTask1 = new SumTask(array, middle, end);
invokeAll(sumTask,sumTask1);
Long join = sumTask.join();
Long join2 = sumTask1.join();
long result = join + join2;
System.out.println("result = " + join + " + " + join2 + " ==> " + result);
return result;
}
}
15、Future
当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。
Future使用:
public class FutureDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
Future<BigDecimal> submit = newFixedThreadPool.submit(new Task());
System.out.println(submit.get());
newFixedThreadPool.shutdown();
}
}
class Task implements Callable<BigDecimal>{
@Override
public BigDecimal call() throws Exception {
TimeUnit.SECONDS.sleep(2);
Double d = 5 + Math.random() * 20;
return new BigDecimal(d).setScale(2,RoundingMode.DOWN);
}
}
CompletableFuture
supplyAsync
public class CompletableFutureDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 有返回值的异步回调 supplyAsync 它需要一个实现了Supplier接口(无参,只有返回值)的对象
// 返回的是错误信息
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 1024;
});
/*
* BigConsumer 有参,无返回值 public CompletableFuture<T> whenComplete( BiConsumer<?
* super T, ? super Throwable> action) { return uniWhenCompleteStage(null,
* action); }
*/
/*
* function 有参,有返回值, 只有一个方法,方法只有一个参数 public CompletableFuture<T> exceptionally(
* Function<Throwable, ? extends T> fn) { return uniExceptionallyStage(fn); }
*/
// 测试返回结果
System.out.println(future.whenComplete((t, u) -> {
System.out.println(t); // t是上面的返回值
System.out.println(u); // u是异常
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get());
}
}
多个CompletableFuture串行执行
多个CompletableFuture并行执行
16、JMM(Java Memory Model)Java内存模型
1、Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,不包括局部变量和方法参数。
2、每个线程有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行,线程不能直接读写主内存中的变量。
3、不同的线程之间也无法访问对方工作内存中的变量,线程之间变量值的传递均需要通过主内存来完成。
三种特性
- 原子性:一个操作不可分割,不可中断,一个线程不会被其他线程干扰。保证原子性:synchronized
- 可见性:
- 有序性:
八种内存交互操作 - lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
- read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
- load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
- use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
- store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
17、Volatile
1、保证可见性
线程的工作内存拥有主物理内存的变量拷贝副本,线程在工作内存(私有)操作变量后—》将变量写回到主物理内存
- —》(第一时间通知)立即通知其它线程该变量值已修改
public class VolatileDemo {
// 保证可见性,不加volatile 线程会进入死循环
private volatile static int num = 0 ;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num == 0) {};
}, "AAA").start();
TimeUnit.SECONDS.sleep(2);
num=1;
System.out.println(num);
}
}
2、不保证原子性
1)使用Lock Synchronized
2)使用原子类(CAS)
3、禁止指令重排
重排序的种类分为三种,分别是:编译器重排序,指令级并行的重排序,内存系统重排序
**原理:内存屏障
18、单例模式
19、CAS(compareAndSwap)比较并交换
AutomicInteger 源码使用了 unsafe类 (CAS 核心类)
unsafe类的方法大部分是native方法
直接操作特定内存的数据,
直接调用操作系统的底层资源执行相应任务
CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
优点:
一致性 并发性
Sybchronized 一致性较高,并发能低 ,
AutomicInteger 在线程数量不是很多的情况下保证了一致性和并发性
缺点:
1、循环时间长,开销大如果CAS一直失败
2、只能保证一个共享变量的原子操作
3、ABA问题
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
atomicInteger.compareAndSet(2020, 2);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2, 3);
System.out.println(atomicInteger.get());
}
}
compareAndSet底层
// 期望值,更新值
public final boolean compareAndSet(int expect, int update) {
// 当前对象,内存偏移量,期望值,更新值
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
ABA问题: 修改之后又修改回来
ABA问题解决: 修改版本号(类似时间戳)
AutomicStampReference 版本号
AtomicReference 普通引用
20、原子引用
ABA问题:
/*Integer有缓存-128~127,该区间的值会从IntegerCache.cache中取,其他会新建对象*/
public class CASDemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> reference = new AtomicStampedReference<Integer>(1, 0);
// 第一个线程的修改
new Thread(() -> {
// 获取版本号
int stamp = reference.getStamp();
System.out.println("A1-->" + stamp);
try {
TimeUnit.SECONDS.sleep(2);// 保证另一个线程获取相同的版本号
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
reference.compareAndSet(1, 2, stamp, reference.getStamp() + 1);
// 第二次修改
System.out.println("A2-->" + reference.getStamp());
reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1);
}).start();
new Thread(() -> {
// 获取版本号
int stamp = reference.getStamp();
System.out.println("B1-->" + stamp);
try {
TimeUnit.SECONDS.sleep(3);// 保证A1修改回值
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(reference.compareAndSet(1, 2, stamp, reference.getStamp() + 1));
}).start();
}
}