目录
前言
本文是《Java并发视频入门》视频课程的笔记总结,旨在帮助更多同学入门并发编程。
本系列共五篇博客,本篇博客着重聊并发编程工具类JUC、Java8与并发。
侵权删。原视频地址:
博客汇总地址:
JUC(Java.util.concurrent)
并发编程使用的工具类,除了线程池、显式锁,还包括原子类、工具类和容器类。
1.Java并发包之原子类
volatile修饰基本/引用类型后,具备了可见性和有序性,但无法保证原子性。原子性可通过Sychronized关键字来保证。执行简单操作加锁过于大材小用。
比如执行i++,可以通过原子类工具集。比如AtomicInteger,这些类是线程安全的。
1.1.AtomicInteger
AtomicInteger是线程安全的,除此之外,还有AtomicBoolean、AtomicLong。
线程不安全代码如下:
public class Test1 {
private static int value = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(value++);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
结果输出:
0
...
10
12
11
11
15
15
...
优化后的线程安全代码:
public class Test1 {
private static AtomicInteger value = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(value.addAndGet(2));
// System.out.println(value.getAndIncrement());
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
结果输出:
2
6
4
8
10
12
...
1.2.AtomicReference
针对对象引用的非阻塞原子性操作。
应用场景:个人银行账号。几点要求:1.个人账号被设计为不可变对象;2.只包含账号名、现金;3.便于验证,资金只增不减。
假设此时有十个人往这个账号打钱,每个人每次打10元,多线程来实现。
线程不安全代码:
public class Test2 {
private static volatile BankAccount bankAccount = new BankAccount("kevin", 0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()-> {
// 读取引用
final BankAccount account = bankAccount;
// 创建新对象
BankAccount newAccount = new BankAccount(account.getAccount(), account.getAmount() + 10);
System.out.println(newAccount);
bankAccount = newAccount;
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class BankAccount {
private final String account;
private final int amount;
public BankAccount(String account, int amount) {
this.account = account;
this.amount = amount;
}
public int getAmount() {
return amount;
}
public String getAccount() {
return account;
}
@Override
public String toString() {
return "BankCount{" +
"account='" + account + '\'' +
", amount=" + amount +
'}';
}
}
结果输出:
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
线程安全代码,但仍有问题:
public class Test2 {
private static AtomicReference<BankAccount> reference = new AtomicReference<>(new BankAccount("kevin", 0));
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 读取引用
final BankAccount account = reference.get();
// 创建新对象
BankAccount newAccount = new BankAccount(account.getAccount(), account.getAmount() + 10);
// CAS更换
if (reference.compareAndSet(account, newAccount)) {
System.out.println(newAccount);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class BankAccount {
private final String account;
private final int amount;
public BankAccount(String account, int amount) {
this.account = account;
this.amount = amount;
}
public int getAmount() {
return amount;
}
public String getAccount() {
return account;
}
@Override
public String toString() {
return "BankCount{" +
"account='" + account + '\'' +
", amount=" + amount +
'}';
}
}
结果输出:
BankCount{account='kevin', amount=40}
BankCount{account='kevin', amount=60}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=20}
BankCount{account='kevin', amount=30}
BankCount{account='kevin', amount=50}
BankCount{account='kevin', amount=80}
BankCount{account='kevin', amount=70}
1.3.AtomicStampedReference
ABA问题:一开始是A、后来变成B,再后来又变成A。那么CAS可能没法进行辨别,如何避免?加入版本号概念,使用AtomicStampedReference。
public class Test3 {
private static AtomicStampedReference<String> reference = new AtomicStampedReference<>("Hello", 0);
public static void main(String[] args) {
reference.compareAndSet("Hello", "world", 0, 1);
String r = Test3.reference.getReference();
System.out.println(r);
}
}
结果输出:
world
1.4.AtomicArray
原子性操作数组,比如AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
public class Test4 {
public static void main(String[] args) {
int []array = {1,2,3,4,5,6,7,8,9};
AtomicIntegerArray integerArray = new AtomicIntegerArray(array);
int andIncrement = integerArray.getAndIncrement(0);
System.out.println(andIncrement);
System.out.println(integerArray);
}
}
结果输出:
1
[2, 2, 3, 4, 5, 6, 7, 8, 9]
2.Java并发包之工具类
2.1.CountDownLatch
子任务均结束后,当前主任务才会进入下一个阶段。
类似于倒计时阀门,有一个门阀等待倒计数,计数器为0时才打开。
eg:计算商品价格。
public class Test5 {
public static void main(String[] args) throws InterruptedException {
int[] productIds = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<ProducePrice> priceList = Arrays.stream(productIds).mapToObj(ProducePrice::new).collect(Collectors.toList());
//创建CountDownLatch,容量为子任务数量
CountDownLatch latch = new CountDownLatch(priceList.size());
//创建子任务
priceList.forEach( p -> {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(p.getProductId() + "开始计算价格");
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
p.setPrice(p.getProductId() * 0.9D);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//计数器减一,子任务减一。
latch.countDown();
}
}
}).start();
});
//await开始等待,主任务阻塞,直到latch.countDown()=0
latch.await();
System.out.println("所有价格计算完毕");
priceList.forEach(System.out::println);
}
private static class ProducePrice {
private int productId;
private double price;
public ProducePrice(int productId) {
this.productId = productId;
}
public ProducePrice(int productId, double price) {
this.price = price;
this.productId = productId;
}
public void setPrice(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
public int getProductId() {
return productId;
}
@Override
public String toString() {
return "ProducePrice{" +
"productId=" + productId +
", price=" + price +
'}';
}
}
}
结果输出:
4开始计算价格
9开始计算价格
10开始计算价格
3开始计算价格
1开始计算价格
5开始计算价格
7开始计算价格
6开始计算价格
8开始计算价格
2开始计算价格
所有价格计算完毕
ProducePrice{productId=1, price=0.9}
ProducePrice{productId=2, price=1.8}
ProducePrice{productId=3, price=2.7}
ProducePrice{productId=4, price=3.6}
ProducePrice{productId=5, price=4.5}
ProducePrice{productId=6, price=5.4}
ProducePrice{productId=7, price=6.3}
ProducePrice{productId=8, price=7.2}
ProducePrice{productId=9, price=8.1}
ProducePrice{productId=10, price=9.0}
2.2.CycilerBarrier
循环屏障,允许多个线程在执行完相应的操作后彼此等待共同到达一个障点。与CountDownLatch类似,有一个不同点:CountDownLatch不可重复利用,CycilerBarrier可重复利用。
快速上手
与CountDownLatch的“快速上手”类似。
public class Test6 {
public static void main(String[] args) {
int[] productIds = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<ProducePrice> priceList = Arrays.stream(productIds).mapToObj(ProducePrice::new).collect(Collectors.toList());
//参数为分片,而非计数器。
CyclicBarrier barrier = new CyclicBarrier(priceList.size());
List<Thread> threadList = new ArrayList<>();
priceList.forEach(p -> {
Thread thread = new Thread(() -> {
try {
System.out.println(p.getProductId() + "开始计算价格");
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
p.setPrice(p.getProductId() * 0.9D);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//等待其他子任务也执行到这个障点
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
threadList.add(thread);
thread.start();
});
threadList.forEach( t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("所有价格计算完毕");
priceList.forEach(System.out::println);
}
private static class ProducePrice {
private int productId;
private double price;
public ProducePrice(int productId) {
this.productId = productId;
}
public ProducePrice(int productId, double price) {
this.price = price;
this.productId = productId;
}
public void setPrice(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
public int getProductId() {
return productId;
}
@Override
public String toString() {
return "ProducePrice{" +
"productId=" + productId +
", price=" + price +
'}';
}
}
}
结果输出:
7开始计算价格
2开始计算价格
9开始计算价格
5开始计算价格
10开始计算价格
1开始计算价格
4开始计算价格
3开始计算价格
8开始计算价格
6开始计算价格
所有价格计算完毕
ProducePrice{productId=1, price=0.9}
ProducePrice{productId=2, price=1.8}
ProducePrice{productId=3, price=2.7}
ProducePrice{productId=4, price=3.6}
ProducePrice{productId=5, price=4.5}
ProducePrice{productId=6, price=5.4}
ProducePrice{productId=7, price=6.3}
ProducePrice{productId=8, price=7.2}
ProducePrice{productId=9, price=8.1}
ProducePrice{productId=10, price=9.0}
循环使用
无需显式重置,内部到零后重置。需要确保能被整除完。
旅游团上车之前(障点)清点人数,到达站点之后(障点)再次清点人数,写个程序模拟下。
public class Test7 {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
for (int i = 0; i < 3; i++) {
new Thread(new Tourist(i, cyclicBarrier)).start();
}
cyclicBarrier.await();
System.out.println("所有人已经上车");
cyclicBarrier.await();
System.out.println("所有人已经下车");
}
private static class Tourist implements Runnable {
private final int id;
private final CyclicBarrier barrier;
public Tourist(int id, CyclicBarrier barrier) {
this.id = id;
this.barrier = barrier;
}
@Override
public void run() {
System.out.println("游客" + id + "上车");
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("游客" + id + "已经上车,等待其他人上车");
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("游客" + id + "等待上车结束");
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("游客" + id + "已经下车,等待其他人下车");
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("游客" + id + "等待下车结束");
}
}
}
结果输出:
游客1上车
游客2上车
游客0上车
游客2已经上车,等待其他人上车
游客0已经上车,等待其他人上车
游客1已经上车,等待其他人上车
所有人已经上车
游客1等待上车结束
游客0等待上车结束
游客2等待上车结束
游客1已经下车,等待其他人下车
游客0已经下车,等待其他人下车
游客2已经下车,等待其他人下车
所有人已经下车
游客2等待下车结束
游客0等待下车结束
游客1等待下车结束
与CountDownLatch的区别:
CountDownLatch:一个线程,等待另外N个线程执行完后才能执行;(一个线程等待多个线程)
CycilerBarrier:N个线程相互等待,任何一个线程完成之前,所有线程必须等待。(多个线程互相等待)
Phaser:
可重复使用的同步屏障,功能非常类似于CycilerBarrier和CountDownLatch的合集。解决了CountDownLatch手动重置的问题;解决了CycilerBarrier一旦制定Size无法改变的问题。一般不建议使用。
2.3.ExChanger
ExChanger简化了两个线程间的数据交互,提供了两个线程的数据交换点。交换两个线程提供给对方的数据。
快速上手:可以看做生产者-消费者模式的实现,关注重点是数据交换。等待3s后交换。
public class Test9 {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T1启动了");
try {
TimeUnit.SECONDS.sleep(1);
String data = exchanger.exchange("我是T1数据");
System.out.println("从T2获取的数据" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T2启动了");
try {
TimeUnit.SECONDS.sleep(3);
String data = exchanger.exchange("我是T2数据");
System.out.println("从T1获取的数据" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
结果输出:
T1启动了
T2启动了
从T1获取的数据:我是T1数据
从T2获取的数据:我是T2数据
3.Java并发包之并发容器
3.1.BlockQueue
元素数量存在界限,队列满时,写操作线程将会被阻塞挂起,队列空时,读操作线程将会被阻塞挂起。类似于生产者-消费者。内部实现主要依赖于显式Lock和Condition。
ArrayBlockQueue是基于数组实现的FIFO阻塞队列,分为两种不同写法:阻塞式和非阻塞式。
3.2.阻塞式写方法
Put:阻塞写操作;Offer:阻塞写,但有超时时间。
public class Test12 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作,若超时1s,即超过指定时间,则写入失败
boolean a = queue.offer("a", 1, TimeUnit.SECONDS);
System.out.println(a);
boolean b = queue.offer("b", 1, TimeUnit.SECONDS);
System.out.println(b);
boolean c = queue.offer("c", 1, TimeUnit.SECONDS);
System.out.println(c);
}
public static void test1(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.put("a");
System.out.println("a添加成功");
queue.put("b");
System.out.println("b添加成功");
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
//读操作
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
queue.put("c");
System.out.println("c添加成功");
}
}
结果输出:
case1:put操作
a添加成功
b添加成功
case1:put操作后take,类似于test1
a添加成功
b添加成功
c添加成功
case2:offer(XX)操作
true
true
false
3.3.非阻塞式写方法
offer/add:非阻塞式写
public class Test12 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作
boolean a = queue.offer("a");
System.out.println(a);
boolean b = queue.offer("b");
System.out.println(b);
boolean c = queue.offer("c");
System.out.println(c);
}
public static void test3(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.add("a");
System.out.println("a添加成功");
queue.add("b");
System.out.println("b添加成功");
queue.add("c");
System.out.println("c添加成功");
}
}
结果输出:
case1:offer方法
true
true
false
case1:put方法
a添加成功
b添加成功
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.base/java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:329)
at com.company.unit9.Test12.main(Test12.java:25)
3.4.阻塞式读方法
take:头部获取数据并移除,当队列为空时,会进入阻塞。
poll:超时时间后,退出阻塞。
public class Test13 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.add("a");
queue.add("b");
String a = queue.poll(1, TimeUnit.SECONDS);
System.out.println(a);
String b = queue.poll(1, TimeUnit.SECONDS);
System.out.println(b);
String c = queue.poll(1, TimeUnit.SECONDS);
System.out.println(c);
}
public static void test1(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.add("a");
queue.add("b");
String a = queue.take();
System.out.println(a);
String b = queue.take();
System.out.println(b);
new Thread(()-> {
try {
TimeUnit.SECONDS.sleep(1);
queue.offer("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
String c = queue.take();
System.out.println(c);
}
}
结果输出:
case1:poll(X,X)方法
a
b
null
case2:take方法,test1
a
b
c
3.5.非阻塞式读方法
poll:非阻塞式读方法,移除数据;
peek:非阻塞式读方法,不会移除数据。
public class Test13 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.add("a");
queue.add("b");
String a = queue.peek();
System.out.println(a);
String b = queue.peek();
System.out.println(b);
String c = queue.peek();
System.out.println(c);
}
public static void test3(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
//写操作, 尾部写入。
queue.add("a");
queue.add("b");
String a = queue.poll();
System.out.println(a);
String b = queue.poll();
System.out.println(b);
String c = queue.poll();
System.out.println(c);
}
}
结果输出:
case1:peek方法
a
a
a
case2:poll方法
a
b
null
3.6.实现生产者-消费者
public class Test14 {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
String s = System.currentTimeMillis() + "";
queue.offer(s);
System.out.println(Thread.currentThread() + "生产数据" + s);
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
try {
String s = queue.take();
System.out.println(Thread.currentThread() + "读取数据" + s);
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
结果输出:
Thread[Thread-7,5,main]生产数据1654479198979
Thread[Thread-3,5,main]生产数据1654479198977
Thread[Thread-15,5,main]读取数据1654479198981
Thread[Thread-12,5,main]读取数据1654479198977
Thread[Thread-11,5,main]读取数据1654479198977
Thread[Thread-17,5,main]读取数据1654479198978
Thread[Thread-0,5,main]生产数据1654479198977
Thread[Thread-15,5,main]读取数据1654479199013
Thread[Thread-6,5,main]生产数据1654479198978
4.小结
本节我们介绍了Java并发包。共分为:原子类(AtomicInteger、AtomicReference、AtomicStampedReference和AtomicArray)、工具类(CountDownLatch、CycilerBarrier和ExChanger)和并发容器(基于ArrayBlockingQueue的阻塞/非阻塞写、阻塞/非阻塞读、生产者-消费者模型)。
Java8与并发
1.并行流与并行排序
1.1.使用并行流过滤数据
public class Test1 {
public static void main(String[] args) {
//流式编程,串行计算
long time1 = System.currentTimeMillis();
long count = IntStream.range(1, 10000000).filter(PrimUtils::isPrime).count();
long time2 = System.currentTimeMillis();
System.out.println(count);
//流式编程,并行流,性能会有提升。
long parallelCount = IntStream.range(1, 10000000).parallel().filter(PrimUtils::isPrime).count();
long time3 = System.currentTimeMillis();
System.out.println(parallelCount);
System.out.println(time2 - time1);
System.out.println(time3- time2);
}
private static class PrimUtils {
public static boolean isPrime(int number) {
int tmp = number;
if (tmp < 2) {
return false;
}
for (int i = 2; Math.sqrt(tmp) >= i; i++) {
if (tmp % i == 0) {
return false;
}
}
return true;
}
}
}
结果输出:
664579
664579
5476
869
1.2.从集合中获取并行流
public class Test2 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
students.add(new Student(new Random().nextInt(100)));
}
//串行流,lambda + 流式编程
double score = students.stream().mapToInt(s -> s.score).average().getAsDouble();
System.out.println(score);
//并行流:parallelStream
double score2 = students.parallelStream().mapToInt(s -> s.score).average().getAsDouble();
System.out.println(score2);
}
private static class Student {
private int score;
private Student(int score) {
this.score = score;
}
}
}
结果输出:
49.546835
49.546835
还有并行排序,从Arrays.sort改为Arrays.parallelSort()。
2.增强的Future:CompletableFuture
2.1.完成了就通知我
public class Test5 {
public static void main(String[] args) throws InterruptedException {
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
new Thread(new AskThread(completableFuture)).start();
TimeUnit.SECONDS.sleep(1);
completableFuture.complete(60);
}
public static class AskThread implements Runnable {
CompletableFuture<Integer> re = null;
public AskThread(CompletableFuture<Integer> re) {
this.re = re;
}
@Override
public void run() {
int myRe = 0;
try {
//get作用:阻塞到complete执行完毕,
myRe = re.get() * re.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(myRe);
}
}
}
结果输出:
3600
2.2.异步执行任务
public class Test51 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
//异步执行,新建线程
return calc(50);
});
//调用get会阻塞
System.out.println(future.get());
}
private static Integer calc(Integer param) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return param * param;
}
}
结果输出:
2500
2.3.流式调用
public class Test6 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//链式调用
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() ->
//异步执行,新建线程
calc(50)
).thenApply(e -> {
System.out.println("在supply Async中处理的值是:" + e);
return Integer.toString(e);
}).thenApply(e -> {
System.out.println("在thenApply中处理的值是:" + e);
return "\""+ e + "\"";
}).thenAccept(System.out::println);
//调用get阻塞,直到CompletableFuture所有任务执行完毕为止。
future.get();
}
private static Integer calc(Integer param) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return param * param;
}
}
结果输出:
在supply Async中处理的值是:2500
在thenApply中处理的值是:2500
"2500"
2.4.处理异常
public class Test6 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//链式调用
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() ->
//异步执行,新建线程
calc(50)
).exceptionally(ex -> {
System.out.println("计算出现异常" + ex);
System.out.println("即将返回零");
return 0;
}).thenApply(e -> {
System.out.println("在supply Async中处理的值是:" + e);
return Integer.toString(e);
}).thenApply(e -> {
System.out.println("在thenApply中处理的值是:" + e);
return "\""+ e + "\"";
}).thenAccept(System.out::println);
//调用get阻塞,直到CompletableFuture所有任务执行完毕为止。
future.get();
}
private static Integer calc(Integer param) {
return param / 0;
}
}
结果输出:
计算出现异常java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
即将返回零
在supply Async中处理的值是:0
在thenApply中处理的值是:0
"0"
3.小结
本节我们介绍了并行流与并行排序、增强的Future:CompletableFuture。
多线程究竟有什么用?
针对百万数据一次性导出、调用高德接口进行路径处理推荐三条路线、上千用户同时导出数据等场景,多线程、线程池(配置两个线程池,一个线程池提供给普通用户,一个线程池提供给VIP用户)会提高执行效率。