跳至主要內容
JUC 八股18 - 并发工具类 (HashTable)

并发工具类

HashTable

alt text
  • Hashtable的底层数据结构主要是数组加上链表,数组是主体,链表是解决hash冲突存在的。

  • HashTable是线程安全的,实现方式是Hashtable的所有公共方法均采用synchronized关键字,当一个线程访问同步方法,另一个线程也访问的时候,就会陷入阻塞或者轮询的状态。

原理

本质就是构建了一个存储了 Entry<?,?> 对象的数组,数据就存放在具体的 Entry 对象里,对应的索引根据 hash 来算


codejavajuc八股大约 7 分钟
JUC 八股20 - 并发工具类

并发工具类

作用
Semaphore 限制线程的数量
Exchanger 两个线程交换数据
CountDownLatch 线程等待直到计数器减为 0 时开始工作
CyclicBarrier 作用跟 CountDownLatch 类似,但是可以重复使用
Phaser 增强的 CyclicBarrier

codejavajuc八股大约 5 分钟
JUC 八股20 - 线程池1(阻塞队列)

线程池

阻塞队列

常用的有五种

  • 有界队列 ArrayBlockingQueue;
  • 无界队列 LinkedBlockingQueue;
  • 优先级队列 PriorityBlockingQueue;
  • 延迟队列 DelayQueue;
  • 同步队列 SynchronousQueue
alt text

阻塞队列接口 BlockingQueue<E>


codejavajuc八股大约 2 分钟
JUC 八股21 - 线程池2

线程池

线程池是用来管理和复用线程的工具,它可以减少线程的创建和销毁开销

在 Java 中,ThreadPoolExecutor 是线程池的核心实现,它通过核心线程数、最大线程数、任务队列和拒绝策略来控制线程的创建和执行

alt text

任务执行流程如下:

任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理

提交任务 → 核心线程是否已满?
  ├─ 未满 → 创建核心线程执行
  └─ 已满 → 任务入队
      ├─ 队列未满 → 等待执行
      └─ 队列已满 → 创建非核心线程
          ├─ 未达最大线程数 → 执行任务
          └─ 已达最大线程数 → 执行拒绝策略

codejavajuc八股大约 6 分钟
JUC 八股22 - 线程池3

线程池

IO 密集 / CPU 密集

任务类型 线程状态 CPU 使用 多线程效果
CPU 密集型 RUNNABLE 可能更慢(切换开销)
IO 密集型 WAITING 更快(等待时间重叠)

codejavajuc八股大约 6 分钟
JUC 八股23 (Future)

Future类

Future 是为了解决多线程执行任务并获取结果的问题

在传统的同步编程中,我们调用一个方法,必须死死等在这个方法那里,直到它执行完毕返回结果,代码才能继续往下走。

而 Future 代表的是一个异步计算的结果

当你把一个耗时的任务(比如网络请求、复杂计算)扔给线程池(ExecutorService)去后台执行时,线程池会立刻塞给你一张“取餐小票”(也就是一个 Future 对象)。

拿到这张小票后,你的主线程不需要原地死等,可以立刻去干别的活儿。等你需要那个耗时任务的结果时,再拿着这张小票去兑换(调用 get() 方法)


codejavajuc八股大约 5 分钟
JUC 八股24 (实际场景)

实际场景

多线程打印奇偶数,怎么控制打印的顺序

使用 ReentrantLock + Condition

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class OddEvenPrinterLock {
  private int count = 1;
  private final int MAX = 10;
  
  // 主线程创建的锁对象
  private final ReentrantLock lock = new ReentrantLock(); 
  // 创建两个条件变量,相当于两个休息室
  private final Condition oddCondition = lock.newCondition();
  private final Condition evenCondition = lock.newCondition();

  public void printOdd() {
      while (count <= MAX) {
          lock.lock(); // 尝试获取锁
          try {
              while (count % 2 == 0) {
                oddCondition.await(); // 奇数线程去奇数休息室睡觉,并释放锁
              }
              if (count <= MAX) {
                System.out.println(Thread.currentThread().getName() + " 打印奇数: " + count);
                count++;
                evenCondition.signal(); // 明确去偶数休息室叫醒偶数线程
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          } finally {
              lock.unlock(); // 切记在 finally 中释放锁
          }
      }
  }

  public void printEven() {
      while (count <= MAX) {
          lock.lock();
          try {
              while (count % 2 != 0) {
                  evenCondition.await(); // 偶数线程去偶数休息室睡觉,并释放锁
              }
              if (count <= MAX) {
                  System.out.println(Thread.currentThread().getName() + " 打印偶数: " + count);
                  count++;
                  oddCondition.signal(); // 明确去奇数休息室叫醒奇数线程
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          } finally {
              lock.unlock();
          }
      }
  }

  public static void main(String[] args) {
      OddEvenPrinterLock printer = new OddEvenPrinterLock();
      new Thread(printer::printOdd, "Thread-Odd").start();
      new Thread(printer::printEven, "Thread-Even").start();
  }
}

codejavajuc八股大约 2 分钟
JUC 八股11 - 锁2(Lock)

Lock 与 Condition 接口

Lock 接口

在JDK 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁

Lock 是 JUC 中的一个接口,最常用的实现类包括可重入锁 ReentrantLock、读写锁 ReentrantReadWriteLock 等

当我们需要加锁时,只需要调用lock()方法,而需要释放锁时,只需要调用unlock()方法

程序运行的最终结果和使用synchronized锁是一样的


codejavajuc八股大约 4 分钟
JUC 八股12 - 锁3(ReentrantLock)

ReentrantLock

synchronized 和 ReentrantLock 的区别

synchronized 由 JVM 内部的 Monitor 机制实现,ReentrantLock基于 AQS 实现

synchronized 可以自动加锁和解锁,ReentrantLock 需要手动 lock() 和 unlock()

alt text

更详细点:

  • ReentrantLock 可以实现多路选择通知,绑定多个 Condition,而 synchronized 只能通过 wait 和 notify 唤醒,属于单路通知
  • synchronized 可以在方法和代码块上加锁,ReentrantLock 只能在代码块上加锁,但可以指定是公平锁还是非公平锁
  • ReentrantLock 提供了一种能够中断等待锁的线程机制,通过 lock.lockInterruptibly() 来实现

codejavajuc八股大约 7 分钟
JUC 八股13 - 锁4(ReentrantLock)

ReentrantLock

lock() 分析

ReentrantLock 对象使用 lock() 方法时,本质是调用其内部的属性 sync 执行 lock,而 sync 就是内部继承了 AQS 的对象

然后进一步,在初始化时,根据对应的构造函数决定采用公平锁 NonfairSync 还是 FairSync, 这两个是继承 Sync,来决定具体的 lock 逻辑


codejavajuc八股大约 9 分钟