1
0
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:
2293736867 2021-06-03 18:48:29 +08:00
parent e646892384
commit 8d9d394270

View File

@ -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,7 +444,7 @@ 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;
@ -453,7 +452,7 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
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.callableRunnable task`)或 `Executors.callableRunnable taskObject resule`)。 工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callableRunnable task`)或 `Executors.callableRunnable taskObject resule`)。
@ -514,31 +512,31 @@ public interface Callable<V> {
1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** 1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future``get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit unit`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future``get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit 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,16 +757,16 @@ 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; private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) { public void execute(Runnable command) {
// 如果任务为null则抛出异常。 // 如果任务为null则抛出异常。
if (command == null) if (command == null)
throw new NullPointerException(); throw new NullPointerException();
@ -799,7 +796,7 @@ pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
//如果addWorker(command, false)执行失败则通过reject()执行相应的拒绝策略的内容。 //如果addWorker(command, false)执行失败则通过reject()执行相应的拒绝策略的内容。
else if (!addWorker(command, false)) else if (!addWorker(command, false))
reject(command); reject(command);
} }
``` ```
通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。 通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
@ -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 的高开销,执行效率大为提升。
@ -1042,7 +1039,7 @@ public class CountDownLatchExample1 {
threadPool.execute(() -> { threadPool.execute(() -> {
try { try {
//处理文件的业务操作 //处理文件的业务操作
...... //......
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -1056,7 +1053,6 @@ public class CountDownLatchExample1 {
threadPool.shutdown(); threadPool.shutdown();
System.out.println("finish"); System.out.println("finish");
} }
} }
``` ```
@ -1075,13 +1071,13 @@ CompletableFuture<Void> task6 =
//自定义业务操作 //自定义业务操作
}); });
...... ......
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. ");
``` ```