mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-07-03 10:46:45 +08:00
multi-thread/2020最新Java并发进阶常见面试题总结 排版修正
This commit is contained in:
parent
e646892384
commit
8d9d394270
@ -379,13 +379,12 @@ Thread Name= 9 formatter = yy-M-d ah:mm
|
|||||||
上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA 会提示你转换为 Java8 的格式(IDEA 真的不错!)。因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法`withInitial()`,将 Supplier 功能接口作为参数。
|
上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA 会提示你转换为 Java8 的格式(IDEA 真的不错!)。因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法`withInitial()`,将 Supplier 功能接口作为参数。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
|
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
|
||||||
@Override
|
@Override
|
||||||
protected SimpleDateFormat initialValue()
|
protected SimpleDateFormat initialValue(){
|
||||||
{
|
return new SimpleDateFormat("yyyyMMdd HHmm");
|
||||||
return new SimpleDateFormat("yyyyMMdd HHmm");
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3. ThreadLocal 原理
|
### 3.3. ThreadLocal 原理
|
||||||
@ -394,13 +393,13 @@ Thread Name= 9 formatter = yy-M-d ah:mm
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
public class Thread implements Runnable {
|
public class Thread implements Runnable {
|
||||||
......
|
//......
|
||||||
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
|
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
|
||||||
ThreadLocal.ThreadLocalMap threadLocals = null;
|
ThreadLocal.ThreadLocalMap threadLocals = null;
|
||||||
|
|
||||||
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
|
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
|
||||||
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
||||||
......
|
//......
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -409,17 +408,17 @@ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
|||||||
`ThreadLocal`类的`set()`方法
|
`ThreadLocal`类的`set()`方法
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public void set(T value) {
|
public void set(T value) {
|
||||||
Thread t = Thread.currentThread();
|
Thread t = Thread.currentThread();
|
||||||
ThreadLocalMap map = getMap(t);
|
ThreadLocalMap map = getMap(t);
|
||||||
if (map != null)
|
if (map != null)
|
||||||
map.set(this, value);
|
map.set(this, value);
|
||||||
else
|
else
|
||||||
createMap(t, value);
|
createMap(t, value);
|
||||||
}
|
}
|
||||||
ThreadLocalMap getMap(Thread t) {
|
ThreadLocalMap getMap(Thread t) {
|
||||||
return t.threadLocals;
|
return t.threadLocals;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。
|
通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。
|
||||||
@ -428,7 +427,7 @@ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
|
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
|
||||||
......
|
//......
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -445,15 +444,15 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
|
|||||||
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
|
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
|
||||||
|
|
||||||
```java
|
```java
|
||||||
static class Entry extends WeakReference<ThreadLocal<?>> {
|
static class Entry extends WeakReference<ThreadLocal<?>> {
|
||||||
/** The value associated with this ThreadLocal. */
|
/** The value associated with this ThreadLocal. */
|
||||||
Object value;
|
Object value;
|
||||||
|
|
||||||
Entry(ThreadLocal<?> k, Object v) {
|
Entry(ThreadLocal<?> k, Object v) {
|
||||||
super(k);
|
super(k);
|
||||||
value = v;
|
value = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**弱引用介绍:**
|
**弱引用介绍:**
|
||||||
@ -478,8 +477,7 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
|
|||||||
|
|
||||||
### 4.2. 实现 Runnable 接口和 Callable 接口的区别
|
### 4.2. 实现 Runnable 接口和 Callable 接口的区别
|
||||||
|
|
||||||
`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5
|
`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。** `Runnable`接口 **不会返回结果或抛出检查异常,但是** `Callable`接口 **可以。所以,如果任务不需要返回结果或抛出异常推荐使用** `Runnable`接口 **,这样代码看起来会更加简洁。
|
||||||
中引入,目的就是为了来处理`Runnable`不支持的用例。`Runnable`**接口不会返回结果或抛出检查异常,但是**`Callable`**接口可以。所以,如果任务不需要返回结果或抛出异常推荐使用**`Runnable`**接口**,这样代码看起来会更加简洁。
|
|
||||||
|
|
||||||
工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
|
工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
|
||||||
|
|
||||||
@ -514,31 +512,31 @@ public interface Callable<V> {
|
|||||||
1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
|
1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
|
||||||
2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
|
2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
|
||||||
|
|
||||||
我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码:
|
我们以** `AbstractExecutorService` **接口中的一个 `submit` 方法为例子来看看源代码:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public Future<?> submit(Runnable task) {
|
public Future<?> submit(Runnable task) {
|
||||||
if (task == null) throw new NullPointerException();
|
if (task == null) throw new NullPointerException();
|
||||||
RunnableFuture<Void> ftask = newTaskFor(task, null);
|
RunnableFuture<Void> ftask = newTaskFor(task, null);
|
||||||
execute(ftask);
|
execute(ftask);
|
||||||
return ftask;
|
return ftask;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
|
上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
|
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
|
||||||
return new FutureTask<T>(runnable, value);
|
return new FutureTask<T>(runnable, value);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们再来看看`execute()`方法:
|
我们再来看看`execute()`方法:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public void execute(Runnable command) {
|
public void execute(Runnable command) {
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.4. 如何创建线程池
|
### 4.4. 如何创建线程池
|
||||||
@ -713,7 +711,6 @@ public class ThreadPoolExecutorDemo {
|
|||||||
System.out.println("Finished all threads");
|
System.out.println("Finished all threads");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
可以看到我们上面的代码指定了:
|
可以看到我们上面的代码指定了:
|
||||||
@ -760,46 +757,46 @@ pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
|
|||||||
**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:
|
**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
|
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
|
||||||
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
|
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
|
||||||
|
|
||||||
private static int workerCountOf(int c) {
|
private static int workerCountOf(int c) {
|
||||||
return c & CAPACITY;
|
return c & CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BlockingQueue<Runnable> workQueue;
|
||||||
|
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
// 如果任务为null,则抛出异常。
|
||||||
|
if (command == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
// ctl 中保存的线程池当前的一些状态信息
|
||||||
|
int c = ctl.get();
|
||||||
|
|
||||||
|
// 下面会涉及到 3 步 操作
|
||||||
|
// 1.首先判断当前线程池中执行的任务数量是否小于 corePoolSize
|
||||||
|
// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
|
||||||
|
if (workerCountOf(c) < corePoolSize) {
|
||||||
|
if (addWorker(command, true))
|
||||||
|
return;
|
||||||
|
c = ctl.get();
|
||||||
}
|
}
|
||||||
|
// 2.如果当前执行的任务数量大于等于 corePoolSize 的时候就会走到这里
|
||||||
private final BlockingQueue<Runnable> workQueue;
|
// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
|
||||||
|
if (isRunning(c) && workQueue.offer(command)) {
|
||||||
public void execute(Runnable command) {
|
int recheck = ctl.get();
|
||||||
// 如果任务为null,则抛出异常。
|
// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
|
||||||
if (command == null)
|
if (!isRunning(recheck) && remove(command))
|
||||||
throw new NullPointerException();
|
|
||||||
// ctl 中保存的线程池当前的一些状态信息
|
|
||||||
int c = ctl.get();
|
|
||||||
|
|
||||||
// 下面会涉及到 3 步 操作
|
|
||||||
// 1.首先判断当前线程池中执行的任务数量是否小于 corePoolSize
|
|
||||||
// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
|
|
||||||
if (workerCountOf(c) < corePoolSize) {
|
|
||||||
if (addWorker(command, true))
|
|
||||||
return;
|
|
||||||
c = ctl.get();
|
|
||||||
}
|
|
||||||
// 2.如果当前执行的任务数量大于等于 corePoolSize 的时候就会走到这里
|
|
||||||
// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
|
|
||||||
if (isRunning(c) && workQueue.offer(command)) {
|
|
||||||
int recheck = ctl.get();
|
|
||||||
// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
|
|
||||||
if (!isRunning(recheck) && remove(command))
|
|
||||||
reject(command);
|
|
||||||
// 如果当前线程池为空就新创建一个线程并执行。
|
|
||||||
else if (workerCountOf(recheck) == 0)
|
|
||||||
addWorker(null, false);
|
|
||||||
}
|
|
||||||
//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
|
|
||||||
//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
|
|
||||||
else if (!addWorker(command, false))
|
|
||||||
reject(command);
|
reject(command);
|
||||||
|
// 如果当前线程池为空就新创建一个线程并执行。
|
||||||
|
else if (workerCountOf(recheck) == 0)
|
||||||
|
addWorker(null, false);
|
||||||
}
|
}
|
||||||
|
//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
|
||||||
|
//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
|
||||||
|
else if (!addWorker(command, false))
|
||||||
|
reject(command);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
|
通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
|
||||||
@ -874,15 +871,15 @@ public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
class AtomicIntegerTest {
|
class AtomicIntegerTest {
|
||||||
private AtomicInteger count = new AtomicInteger();
|
private AtomicInteger count = new AtomicInteger();
|
||||||
//使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
|
//使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
|
||||||
public void increment() {
|
public void increment() {
|
||||||
count.incrementAndGet();
|
count.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return count.get();
|
return count.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -894,18 +891,18 @@ AtomicInteger 线程安全原理简单分析
|
|||||||
AtomicInteger 类的部分源码:
|
AtomicInteger 类的部分源码:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
|
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
|
||||||
private static final Unsafe unsafe = Unsafe.getUnsafe();
|
private static final Unsafe unsafe = Unsafe.getUnsafe();
|
||||||
private static final long valueOffset;
|
private static final long valueOffset;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
valueOffset = unsafe.objectFieldOffset
|
valueOffset = unsafe.objectFieldOffset
|
||||||
(AtomicInteger.class.getDeclaredField("value"));
|
(AtomicInteger.class.getDeclaredField("value"));
|
||||||
} catch (Exception ex) { throw new Error(ex); }
|
} catch (Exception ex) { throw new Error(ex); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private volatile int value;
|
private volatile int value;
|
||||||
```
|
```
|
||||||
|
|
||||||
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
|
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
|
||||||
@ -954,15 +951,15 @@ private volatile int state;//共享变量,使用volatile修饰保证线程可
|
|||||||
|
|
||||||
//返回同步状态的当前值
|
//返回同步状态的当前值
|
||||||
protected final int getState() {
|
protected final int getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
// 设置同步状态的值
|
// 设置同步状态的值
|
||||||
protected final void setState(int newState) {
|
protected final void setState(int newState) {
|
||||||
state = newState;
|
state = newState;
|
||||||
}
|
}
|
||||||
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
|
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
|
||||||
protected final boolean compareAndSetState(int expect, int update) {
|
protected final boolean compareAndSetState(int expect, int update) {
|
||||||
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
|
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1030,33 +1027,32 @@ tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
public class CountDownLatchExample1 {
|
public class CountDownLatchExample1 {
|
||||||
// 处理文件的数量
|
// 处理文件的数量
|
||||||
private static final int threadCount = 6;
|
private static final int threadCount = 6;
|
||||||
|
|
||||||
public static void main(String[] args) throws InterruptedException {
|
public static void main(String[] args) throws InterruptedException {
|
||||||
// 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建)
|
// 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建)
|
||||||
ExecutorService threadPool = Executors.newFixedThreadPool(10);
|
ExecutorService threadPool = Executors.newFixedThreadPool(10);
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
|
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
|
||||||
for (int i = 0; i < threadCount; i++) {
|
for (int i = 0; i < threadCount; i++) {
|
||||||
final int threadnum = i;
|
final int threadnum = i;
|
||||||
threadPool.execute(() -> {
|
threadPool.execute(() -> {
|
||||||
try {
|
try {
|
||||||
//处理文件的业务操作
|
//处理文件的业务操作
|
||||||
......
|
//......
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
//表示一个文件已经被完成
|
//表示一个文件已经被完成
|
||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
countDownLatch.await();
|
||||||
});
|
threadPool.shutdown();
|
||||||
|
System.out.println("finish");
|
||||||
}
|
}
|
||||||
countDownLatch.await();
|
|
||||||
threadPool.shutdown();
|
|
||||||
System.out.println("finish");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1066,22 +1062,22 @@ public class CountDownLatchExample1 {
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
CompletableFuture<Void> task1 =
|
CompletableFuture<Void> task1 =
|
||||||
CompletableFuture.supplyAsync(()->{
|
CompletableFuture.supplyAsync(()->{
|
||||||
//自定义业务操作
|
//自定义业务操作
|
||||||
});
|
});
|
||||||
......
|
......
|
||||||
CompletableFuture<Void> task6 =
|
CompletableFuture<Void> task6 =
|
||||||
CompletableFuture.supplyAsync(()->{
|
CompletableFuture.supplyAsync(()->{
|
||||||
//自定义业务操作
|
//自定义业务操作
|
||||||
});
|
});
|
||||||
......
|
......
|
||||||
CompletableFuture<Void> headerFuture=CompletableFuture.allOf(task1,.....,task6);
|
CompletableFuture<Void> headerFuture=CompletableFuture.allOf(task1,.....,task6);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
headerFuture.join();
|
headerFuture.join();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
......
|
//......
|
||||||
}
|
}
|
||||||
System.out.println("all done. ");
|
System.out.println("all done. ");
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1092,11 +1088,11 @@ System.out.println("all done. ");
|
|||||||
List<String> filePaths = Arrays.asList(...)
|
List<String> filePaths = Arrays.asList(...)
|
||||||
// 异步处理所有文件
|
// 异步处理所有文件
|
||||||
List<CompletableFuture<String>> fileFutures = filePaths.stream()
|
List<CompletableFuture<String>> fileFutures = filePaths.stream()
|
||||||
.map(filePath -> doSomeThing(filePath))
|
.map(filePath -> doSomeThing(filePath))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 将他们合并起来
|
// 将他们合并起来
|
||||||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
||||||
fileFutures.toArray(new CompletableFuture[fileFutures.size()])
|
fileFutures.toArray(new CompletableFuture[fileFutures.size()])
|
||||||
);
|
);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user