diff --git a/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md b/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md index 190c47d4..4008126d 100644 --- a/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md +++ b/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md @@ -379,13 +379,12 @@ Thread Name= 9 formatter = yy-M-d ah:mm 上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA 会提示你转换为 Java8 的格式(IDEA 真的不错!)。因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法`withInitial()`,将 Supplier 功能接口作为参数。 ```java - private static final ThreadLocal formatter = new ThreadLocal(){ - @Override - protected SimpleDateFormat initialValue() - { - return new SimpleDateFormat("yyyyMMdd HHmm"); - } - }; +private static final ThreadLocal formatter = new ThreadLocal(){ + @Override + protected SimpleDateFormat initialValue(){ + return new SimpleDateFormat("yyyyMMdd HHmm"); + } +}; ``` ### 3.3. ThreadLocal 原理 @@ -394,13 +393,13 @@ Thread Name= 9 formatter = yy-M-d ah:mm ```java public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; + //...... + //与此线程有关的ThreadLocal值。由ThreadLocal类维护 + ThreadLocal.ThreadLocalMap threadLocals = null; -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... + //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 + ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; + //...... } ``` @@ -409,17 +408,17 @@ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; `ThreadLocal`类的`set()`方法 ```java - public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); - } - ThreadLocalMap getMap(Thread t) { - return t.threadLocals; - } +public void set(T value) { + Thread t = Thread.currentThread(); + ThreadLocalMap map = getMap(t); + if (map != null) + map.set(this, value); + else + createMap(t, value); +} +ThreadLocalMap getMap(Thread t) { + return t.threadLocals; +} ``` 通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。 @@ -428,7 +427,7 @@ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ```java 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()`方法 ```java - static class Entry extends WeakReference> { - /** The value associated with this ThreadLocal. */ - Object value; +static class Entry extends WeakReference> { + /** The value associated with this ThreadLocal. */ + Object value; - Entry(ThreadLocal k, Object v) { - super(k); - value = v; - } - } + Entry(ThreadLocal k, Object v) { + super(k); + value = v; + } +} ``` **弱引用介绍:** @@ -478,8 +477,7 @@ ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { ### 4.2. 实现 Runnable 接口和 Callable 接口的区别 -`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 -中引入,目的就是为了来处理`Runnable`不支持的用例。`Runnable`**接口不会返回结果或抛出检查异常,但是**`Callable`**接口可以。所以,如果任务不需要返回结果或抛出异常推荐使用**`Runnable`**接口**,这样代码看起来会更加简洁。 +`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。** `Runnable`接口 **不会返回结果或抛出检查异常,但是** `Callable`接口 **可以。所以,如果任务不需要返回结果或抛出异常推荐使用** `Runnable`接口 **,这样代码看起来会更加简洁。 工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。 @@ -514,31 +512,31 @@ public interface Callable { 1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** 2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 -我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码: +我们以** `AbstractExecutorService` **接口中的一个 `submit` 方法为例子来看看源代码: ```java - public Future submit(Runnable task) { - if (task == null) throw new NullPointerException(); - RunnableFuture ftask = newTaskFor(task, null); - execute(ftask); - return ftask; - } +public Future submit(Runnable task) { + if (task == null) throw new NullPointerException(); + RunnableFuture ftask = newTaskFor(task, null); + execute(ftask); + return ftask; +} ``` 上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。 ```java - protected RunnableFuture newTaskFor(Runnable runnable, T value) { - return new FutureTask(runnable, value); - } +protected RunnableFuture newTaskFor(Runnable runnable, T value) { + return new FutureTask(runnable, value); +} ``` 我们再来看看`execute()`方法: ```java - public void execute(Runnable command) { - ... - } +public void execute(Runnable command) { + ... +} ``` ### 4.4. 如何创建线程池 @@ -713,7 +711,6 @@ public class ThreadPoolExecutorDemo { 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)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码: ```java - // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount) - private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); +// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount) +private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); - private static int workerCountOf(int c) { - return c & CAPACITY; +private static int workerCountOf(int c) { + return c & CAPACITY; +} + +private final BlockingQueue 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(); } - - private final BlockingQueue 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 的时候就会走到这里 - // 通过 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)) + // 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); +} ``` 通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。 @@ -874,15 +871,15 @@ public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet ```java class AtomicIntegerTest { - private AtomicInteger count = new AtomicInteger(); - //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 - public void increment() { - count.incrementAndGet(); - } + private AtomicInteger count = new AtomicInteger(); + //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 + public void increment() { + count.incrementAndGet(); + } - public int getCount() { - return count.get(); - } + public int getCount() { + return count.get(); + } } ``` @@ -894,18 +891,18 @@ AtomicInteger 线程安全原理简单分析 AtomicInteger 类的部分源码: ```java - // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long valueOffset; +// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) +private static final Unsafe unsafe = Unsafe.getUnsafe(); +private static final long valueOffset; - static { - try { - valueOffset = unsafe.objectFieldOffset - (AtomicInteger.class.getDeclaredField("value")); - } catch (Exception ex) { throw new Error(ex); } - } +static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } +} - private volatile int value; +private volatile int value; ``` AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 @@ -954,15 +951,15 @@ private volatile int state;//共享变量,使用volatile修饰保证线程可 //返回同步状态的当前值 protected final int getState() { - return state; + return state; } // 设置同步状态的值 protected final void setState(int newState) { - state = newState; + state = newState; } //原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) 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 public class CountDownLatchExample1 { - // 处理文件的数量 - private static final int threadCount = 6; + // 处理文件的数量 + private static final int threadCount = 6; - public static void main(String[] args) throws InterruptedException { - // 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建) - ExecutorService threadPool = Executors.newFixedThreadPool(10); - final CountDownLatch countDownLatch = new CountDownLatch(threadCount); - for (int i = 0; i < threadCount; i++) { - final int threadnum = i; - threadPool.execute(() -> { - try { - //处理文件的业务操作 - ...... - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - //表示一个文件已经被完成 - countDownLatch.countDown(); + public static void main(String[] args) throws InterruptedException { + // 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建) + ExecutorService threadPool = Executors.newFixedThreadPool(10); + final CountDownLatch countDownLatch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + final int threadnum = i; + threadPool.execute(() -> { + try { + //处理文件的业务操作 + //...... + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + //表示一个文件已经被完成 + 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 CompletableFuture task1 = - CompletableFuture.supplyAsync(()->{ - //自定义业务操作 - }); + CompletableFuture.supplyAsync(()->{ + //自定义业务操作 + }); ...... CompletableFuture task6 = - CompletableFuture.supplyAsync(()->{ + CompletableFuture.supplyAsync(()->{ //自定义业务操作 - }); + }); ...... - CompletableFuture headerFuture=CompletableFuture.allOf(task1,.....,task6); +CompletableFuture headerFuture=CompletableFuture.allOf(task1,.....,task6); - try { +try { headerFuture.join(); - } catch (Exception ex) { - ...... - } +} catch (Exception ex) { + //...... +} System.out.println("all done. "); ``` @@ -1092,11 +1088,11 @@ System.out.println("all done. "); List filePaths = Arrays.asList(...) // 异步处理所有文件 List> fileFutures = filePaths.stream() - .map(filePath -> doSomeThing(filePath)) - .collect(Collectors.toList()); + .map(filePath -> doSomeThing(filePath)) + .collect(Collectors.toList()); // 将他们合并起来 CompletableFuture allFutures = CompletableFuture.allOf( - fileFutures.toArray(new CompletableFuture[fileFutures.size()]) + fileFutures.toArray(new CompletableFuture[fileFutures.size()]) ); ```