本文最后更新于 2 分钟前,文中所描述的信息可能已发生改变。
并发编程允许程序同时执行多个任务,提高程序效率。Java 通过多线程实现并发。主要类和接口包括:
- Thread 类:表示线程的类,通过继承 Thread 类来创建线程。
- Runnable 接口:表示任务的接口,通过实现 Runnable 接口来创建线程。
- Callable 接口:表示有返回值的任务的接口,通过实现 Callable 接口来创建线程。
- Executor 框架:用于管理线程池和任务调度,如 ExecutorService、ThreadPoolExecutor。
详情
volatile 是 Java 提供的一种轻量级的同步机制,它具有两个特性:可见性和有序性。
- 可见性:volatile 保证了线程间变量是可见的,即当一个线程修改了共享变量后,该变量会立马同步到主内存,其余线程监听到数据变化后会使得自己缓存的原数据失效,并触发 read 操作读取新修改的变量的值。
- 有序性:volatile 保证了线程对变量的修改是有序的,即禁止指令重排序。
- 当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
- 在进行指令优化时,不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。
- 应用场景
- volatile 不能保证原子性,即不能保证多个线程同时修改一个变量时的线程安全性。
- volatile 不能代替锁,它只能保证可见性和有序性,不能保证原子性。
- volatile 适用于一个线程写,多个线程读的场景。
详情
- 继承 Thread 类
java
public class MyThread extends Thread {
@Override
public void run() {
// ...
}
}
- 实现 Runnable 接口
java
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
- 实现 Callable 接口
java
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// ...
return 0;
}
}
- 使用线程池
java
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new MyRunnable());
Future<Integer> future = executor.submit(new MyCallable());
详情
方式 | 是否支持返回值 | 是否可复用 | 应用场景 |
---|---|---|---|
继承 Thread 类 | 不支持 | 不可复用 | 简单场景 |
实现 Runnable 接口 | 不支持 | 可复用 | 多线程共享资源 |
实现 Callable 接口 | 支持 | 可复用 | 需要返回值的任务 |
使用线程池 | 支持 | 可复用 | 高并发场景 |
详情
类别 | 类名 | 说明 |
---|---|---|
基础类 | String, StringBuffer, AtomicInteger | StringBuffer 使用了同步方法,线程安全 |
集合类 | Vector, ConcurrentHashMap, CopyOnWriteArrayList | 使用同步或锁机制保证线程安全 |
工具类 | Collections.synchronizedList | 通过包装实现线程安全的集合 |
Blocking 队列 | LinkedBlockingQueue, ArrayBlockingQueue | 支持生产者-消费者模型,线程安全 |
原子类 | AtomicInteger, AtomicLong, AtomicReference | 使用 CAS(Compare And Swap)实现原子操作 |
锁机制
详情
- 悲观锁基于悲观的假设,认为共享资源在每次访问时都会发生冲突,因此在每次操作时都会加锁。 这种锁机制会导致其他线程阻塞,直到锁被释放。Java 中的 synchronized 和 ReentrantLock 是悲观锁的典型实现方式。 虽然悲观锁能有效避免数据竞争,但在高并发场景下会导致线程阻塞、上下文切换频繁,从而影响系统性能,并且还可能引发死锁问题。
- 乐观锁基于乐观的假设,认为共享资源在每次访问时不会发生冲突,因此无须加锁,只需在提交修改时验证数据是否被其他线程修改。 Java 中的 AtomicInteger 和 LongAdder 等类通过 CAS(Compare-And-Swap)算法实现了乐观锁。 乐观锁避免了线程阻塞和死锁问题,在读多写少的场景中性能优越。但在写操作频繁的情况下,可能会导致大量重试和失败,从而影响性能。
- 著作权归 JavaGuide(javaguide.cn)所有
- 基于 MIT 协议
- 原文链接:https://javaguide.cn/java/concurrent/optimistic-lock-and-pessimistic-lock.html
详情
- 公平锁:按照线程请求的顺序来获取锁,即先到先得。
- 非公平锁:不考虑线程请求的顺序,有可能后请求的线程先获取到锁。
CAS (Compare And Swap)
- 定义:CAS 是一种乐观锁,通过比较当前值和期望值是否一样来决定是否更新。
- 相关类:AtomicInteger、AtomicLong、AtomicReference 等。
- 相关问题
问题 | 解决方案 |
---|---|
ABA 问题 | 使用版本号或时间戳解决,如 AtomicStampedReference |
循环时间长开销大 | 限制重试次数,避免无限循环,如自旋锁 |
只能保证一个共享变量的原子操作 | 使用 AtomicReference 或加锁 |
ABA 问题:线程 1 读取数据 A,线程 2 将 A 改为 B,再改回 A,线程 1 再次读取 A,认为 A 未变化。
synchronized
- 定义:synchronized 是一种悲观锁,通过获取锁来保证同一时刻只有一个线程执行。
- 特点:可重入、非公平锁、不可中断、性能高。
- 使用场景:适用于代码块同步、单例模式、简单场景。
ReentrantLock
- 定义:ReentrantLock 是一种可重入锁,通过 AQS 实现。
- 特点:支持公平/非公平锁、可中断、超时获取。
- 使用场景:适用于复杂场景,如高并发、需要公平锁、可中断、超时获取或多个条件变量等场景。
详情
- 区别
对比项 | synchronized | ReentrantLock |
---|---|---|
底层实现 | JVM 内置,基于 Monitor | JDK 提供的 API,基于 AQS(AbstractQueuedSynchronizer) |
锁类型 | 悲观锁,只能独占(排他锁) | 支持公平/非公平锁、可中断、超时获取 |
可重入性 | ✅ 支持 | ✅ 支持 |
公平性 | ❌ 非公平 | ✅ 支持公平锁和非公平锁 |
锁的范围 | 作用在方法或代码块 | 灵活,作用于任意代码块 |
是否可中断 | ❌ 不可中断 | ✅ 可以中断 lockInterruptibly() |
是否可超时 | ❌ 不支持超时获取锁 | ✅ tryLock(timeout) 支持超时等待 |
是否需要手动释放 | ❌ 自动释放(异常时也会释放) | ✅ 需要 lock() 和 unlock() 手动释放 |
性能 | JVM 内部优化,性能高 | 适用于复杂场景,但开销稍大 |
- 使用场景
- synchronized:适用于代码块同步,单例模式,简单场景下使用,性能较高。
- ReentrantLock:适用于复杂场景,如高并发,需要公平锁、可中断、超时获取或多个条件变量等场景。
- 注意事项
- synchronized: 无需手动释放锁,异常时会自动释放锁,代码块优先级高于方法
- ReentrantLock: 需要手动释放锁,finally 块中释放锁,条件变量不易过多使用,影响性能