并发工具类
HashTable
-
Hashtable的底层数据结构主要是数组加上链表,数组是主体,链表是解决hash冲突存在的。
-
HashTable是线程安全的,实现方式是Hashtable的所有公共方法均采用synchronized关键字,当一个线程访问同步方法,另一个线程也访问的时候,就会陷入阻塞或者轮询的状态。
原理
本质就是构建了一个存储了 Entry<?,?> 对象的数组,数据就存放在具体的 Entry 对象里,对应的索引根据 hash 来算
Hashtable的底层数据结构主要是数组加上链表,数组是主体,链表是解决hash冲突存在的。
HashTable是线程安全的,实现方式是Hashtable的所有公共方法均采用synchronized关键字,当一个线程访问同步方法,另一个线程也访问的时候,就会陷入阻塞或者轮询的状态。
本质就是构建了一个存储了 Entry<?,?> 对象的数组,数据就存放在具体的 Entry 对象里,对应的索引根据 hash 来算
| 类 | 作用 |
|---|---|
| Semaphore | 限制线程的数量 |
| Exchanger | 两个线程交换数据 |
| CountDownLatch | 线程等待直到计数器减为 0 时开始工作 |
| CyclicBarrier | 作用跟 CountDownLatch 类似,但是可以重复使用 |
| Phaser | 增强的 CyclicBarrier |
常用的有五种
BlockingQueue<E>线程池是用来管理和复用线程的工具,它可以减少线程的创建和销毁开销
在 Java 中,ThreadPoolExecutor 是线程池的核心实现,它通过核心线程数、最大线程数、任务队列和拒绝策略来控制线程的创建和执行
任务执行流程如下:
任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理
提交任务 → 核心线程是否已满?
├─ 未满 → 创建核心线程执行
└─ 已满 → 任务入队
├─ 队列未满 → 等待执行
└─ 队列已满 → 创建非核心线程
├─ 未达最大线程数 → 执行任务
└─ 已达最大线程数 → 执行拒绝策略
| 任务类型 | 线程状态 | CPU 使用 | 多线程效果 |
|---|---|---|---|
| CPU 密集型 | RUNNABLE | 高 | 可能更慢(切换开销) |
| IO 密集型 | WAITING | 低 | 更快(等待时间重叠) |
Future 是为了解决多线程执行任务并获取结果的问题
在传统的同步编程中,我们调用一个方法,必须死死等在这个方法那里,直到它执行完毕返回结果,代码才能继续往下走。
而 Future 代表的是一个异步计算的结果。
当你把一个耗时的任务(比如网络请求、复杂计算)扔给线程池(ExecutorService)去后台执行时,线程池会立刻塞给你一张“取餐小票”(也就是一个 Future 对象)。
拿到这张小票后,你的主线程不需要原地死等,可以立刻去干别的活儿。等你需要那个耗时任务的结果时,再拿着这张小票去兑换(调用 get() 方法)
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();
}
}
在JDK 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁
Lock 是 JUC 中的一个接口,最常用的实现类包括可重入锁 ReentrantLock、读写锁 ReentrantReadWriteLock 等
当我们需要加锁时,只需要调用lock()方法,而需要释放锁时,只需要调用unlock()方法
程序运行的最终结果和使用synchronized锁是一样的
synchronized 由 JVM 内部的 Monitor 机制实现,ReentrantLock基于 AQS 实现
synchronized 可以自动加锁和解锁,ReentrantLock 需要手动 lock() 和 unlock()
更详细点:
ReentrantLock 对象使用 lock() 方法时,本质是调用其内部的属性 sync 执行 lock,而 sync 就是内部继承了 AQS 的对象
然后进一步,在初始化时,根据对应的构造函数决定采用公平锁 NonfairSync 还是 FairSync, 这两个是继承 Sync,来决定具体的 lock 逻辑