diff --git a/docs/java/collection/java-collection-questions-02.md b/docs/java/collection/java-collection-questions-02.md index dabb7581..e8b1b9dd 100644 --- a/docs/java/collection/java-collection-questions-02.md +++ b/docs/java/collection/java-collection-questions-02.md @@ -285,11 +285,11 @@ final void treeifyBin(Node[] tab, int hash) { ### HashMap 的长度为什么是 2 的幂次方 -为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。 +为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。 **这个算法应该如何设计呢?** -我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。** +我们首先可能会想到采用 % 取余的操作来实现。但是,重点来了:“**取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作**(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 **采用二进制位操作 & 相对于 % 能够提高运算效率**,这就解释了 HashMap 的长度为什么是 2 的幂次方。 ### HashMap 多线程操作导致死循环问题 diff --git a/docs/java/concurrent/java-concurrent-questions-03.md b/docs/java/concurrent/java-concurrent-questions-03.md index 798754fa..59de02be 100644 --- a/docs/java/concurrent/java-concurrent-questions-03.md +++ b/docs/java/concurrent/java-concurrent-questions-03.md @@ -317,6 +317,15 @@ public ScheduledThreadPoolExecutor(int corePoolSize) { ![线程池各个参数的关系](https://oss.javaguide.cn/github/javaguide/java/concurrent/relationship-between-thread-pool-parameters.png) +### 线程池的核心线程会被回收吗? + +`ThreadPoolExecutor` 默认不会回收核心线程,即使它们已经空闲了。这是为了减少创建线程的开销,因为核心线程通常是要长期保持活跃的。但是,如果线程池是被用于周期性使用的场景,且频率不高(周期之间有明显的空闲时间),可以考虑将 `allowCoreThreadTimeOut(boolean value)` 方法的参数设置为 `true`,这样就会回收空闲(时间间隔由 `keepAliveTime` 指定)的核心线程了。 + +```java + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 6, 6, TimeUnit.SECONDS, new SynchronousQueue<>()); + threadPoolExecutor.allowCoreThreadTimeOut(true); +``` + ### 线程池的拒绝策略有哪些? 如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,`ThreadPoolExecutor` 定义一些策略: @@ -518,6 +527,13 @@ new RejectedExecutionHandler() { 3. 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。 4. 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,拒绝策略会调用`RejectedExecutionHandler.rejectedExecution()`方法。 +再提一个有意思的小问题:**线程池在提交任务前,可以提前创建线程吗?** + +答案是可以的!`ThreadPoolExecutor` 提供了两个方法帮助我们在提交任务之前,完成核心线程的创建,从而实现线程池预热的效果: + +- `prestartCoreThread()`:启动一个线程,等待任务,如果已达到核心线程数,这个方法返回 false,否则返回 true; +- `prestartAllCoreThreads()`:启动所有的核心线程,并返回启动成功的核心线程数。 + ### 线程池中线程异常后,销毁还是复用? 先说结论,需要分两种情况: @@ -1151,6 +1167,7 @@ public int await() throws InterruptedException, BrokenBarrierException { - 《深入理解 Java 虚拟机》 - 《实战 Java 高并发程序设计》 +- Java线程池的实现原理及其在业务中的最佳实践:阿里云开发者: - 带你了解下 SynchronousQueue(并发队列专题): - 阻塞队列 — DelayedWorkQueue 源码分析: - Java 多线程(三)——FutureTask/CompletableFuture: