12. Java常见锁
- synchronized: 内置锁
- ReentrantLock: 可重入锁
- ReadWriteLock: 读写锁
- StampedLock: 邮戳锁
- ReentrantReadWriteLock: 可重入读写锁
让我用生动的例子来解释这些不同类型的锁。
1. Synchronized (内置锁) - 像单人卫生间 🚽
public class BathRoom {
public synchronized void use(String person) {
System.out.println(person + "进入卫生间");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(person + "离开卫生间");
}
}
特点:
- 一次只能一个人使用
- 自动加锁解锁
- 不能中断等待
- 不能尝试获取锁
sequenceDiagram
participant A as 人员A
participant L as 卫生间
participant B as 人员B
A->>L: 进入并锁门
B->>L: 等待
A->>L: 使用完毕
A->>L: 解锁离开
B->>L: 进入并锁门
2. ReentrantLock (可重入锁) - 像高级卫生间 🚾
public class ModernBathRoom {
private final ReentrantLock lock = new ReentrantLock();
public void use(String person) {
// 尝试获取锁,等待5秒
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
System.out.println(person + "进入卫生间");
Thread.sleep(1000);
} finally {
lock.unlock();
System.out.println(person + "离开卫生间");
}
} else {
System.out.println(person + "等太久了,去别的卫生间");
}
} catch (InterruptedException e) {
System.out.println(person + "被打断了");
}
}
}
特点:
- 可以尝试获取锁
- 可以设置等待时间
- 可以中断等待
- 可以公平排队
- 需要手动解锁
3. ReadWriteLock (读写锁) - 像图书馆阅览室 📚
public class Library {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private String book = "原始内容";
// 多人可以同时读
public void readBook(String person) {
readLock.lock();
try {
System.out.println(person + "正在读书: " + book);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
// 只能一个人写
public void writeBook(String person, String content) {
writeLock.lock();
try {
System.out.println(person + "正在写书");
book = content;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
graph TD
A[图书馆] --> B[读锁]
A --> C[写锁]
B --> D[多人可同时读]
C --> E[一人写时独占]
D --> F[读者1]
D --> G[读者2]
D --> H[读者3]
E --> I[写者]
4. StampedLock (邮戳锁) - 像现代博物馆参观系统 🏛️
public class Museum {
private final StampedLock lock = new StampedLock();
private double treasureLocation = 0.0;
// 乐观读(参观者拍照,不影响展品移动)
public double viewTreasure(String person) {
long stamp = lock.tryOptimisticRead();
double currentLocation = treasureLocation;
if (!lock.validate(stamp)) {
// 发现展品正在移动,切换到悲观读
stamp = lock.readLock();
try {
currentLocation = treasureLocation;
} finally {
lock.unlockRead(stamp);
}
}
return currentLocation;
}
// 写锁(工作人员移动展品)
public void moveTreasure(double newLocation) {
long stamp = lock.writeLock();
try {
treasureLocation = newLocation;
} finally {
lock.unlockWrite(stamp);
}
}
}
特点:
- 支持乐观读
- 性能更好
- 不可重入
- 不支持条件变量
5. ReentrantReadWriteLock (可重入读写锁) - 像高级图书馆系统 📖
public class AdvancedLibrary {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<String, String> books = new HashMap<>();
// 查看书籍目录
public String viewBook(String bookName) {
lock.readLock().lock();
try {
return books.get(bookName);
} finally {
lock.readLock().unlock();
}
}
// 更新书籍内容
public void updateBook(String bookName, String content) {
lock.writeLock().lock();
try {
books.put(bookName, content);
} finally {
lock.writeLock().unlock();
}
}
// 整理书籍(需要同时获取读锁和写锁)
public void organizeBooks() {
// 可重入特性,允许同一个线程获取多个锁
lock.writeLock().lock();
try {
lock.readLock().lock();
try {
// 整理书籍逻辑
} finally {
lock.readLock().unlock();
}
} finally {
lock.writeLock().unlock();
}
}
}
6. 锁的对比图
graph TB
A[Java锁类型] --> B[Synchronized]
A --> C[ReentrantLock]
A --> D[ReadWriteLock]
A --> E[StampedLock]
B --> B1[简单易用]
B --> B2[自动解锁]
B --> B3[不可中断]
C --> C1[灵活可控]
C --> C2[可中断]
C --> C3[可设超时]
D --> D1[读写分离]
D --> D2[共享/独占]
D --> D3[可重入]
E --> E1[乐观读]
E --> E2[高性能]
E --> E3[不可重入]
7. 使用场景对比
| 锁类型 | 适用场景 | 比喻 |
|---|---|---|
| Synchronized | 简单同步 | 普通卫生间 |
| ReentrantLock | 需要灵活控制 | 高级卫生间 |
| ReadWriteLock | 读多写少 | 图书馆 |
| StampedLock | 高性能场景 | 现代博物馆 |
| ReentrantReadWriteLock | 复杂读写场景 | 高级图书馆 |
8. 性能对比示例
public class LockPerformanceTest {
private static final int THREAD_COUNT = 100;
private static final int OPERATION_COUNT = 10000;
// 测试不同锁的性能
public static void main(String[] args) throws InterruptedException {
testLock(new SynchronizedLock());
testLock(new ReentrantLockWrapper());
testLock(new ReadWriteLockWrapper());
testLock(new StampedLockWrapper());
}
private static void testLock(LockInterface lock) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long startTime = System.currentTimeMillis();
// 创建多个线程测试
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
lock.read();
if (j % 100 == 0) {
lock.write();
}
}
latch.countDown();
}).start();
}
latch.await();
long endTime = System.currentTimeMillis();
System.out.println(lock.getClass().getSimpleName() +
" 耗时: " + (endTime - startTime) + "ms");
}
}
记住:
- Synchronized: 简单场景首选
- ReentrantLock: 需要灵活控制时使用
- ReadWriteLock: 读多写少的场景
- StampedLock: 追求极致性能时使用
- ReentrantReadWriteLock: 复杂读写场景使用
13. sleep()和wait()的区别
-
sleep():
- 不释放锁
- Thread类的方法
- 到时间自动恢复
-
wait():
- 释放锁
- Object类的方法
- 需要notify/notifyAll唤醒
14. 悲观锁和乐观锁
-
悲观锁:假设会发生并发冲突,访问时都要加锁
- 例如:synchronized、ReentrantLock
-
乐观锁:假设不会发生并发冲突,更新时检查是否有冲突
- 例如:CAS操作、版本号机制
15. BlockingQueue
阻塞队列特点:
- 队列满时,入队阻塞
- 队列空时,出队阻塞
常见实现:
- ArrayBlockingQueue:有界队列
- LinkedBlockingQueue:可选有界队列
- PriorityBlockingQueue:优先级队列
- DelayQueue:延迟队列
让我用一个餐厅点餐系统来生动地解释 BlockingQueue 的实现和使用。
1. BlockingQueue 家族图示
graph TD
A[BlockingQueue] --> B[ArrayBlockingQueue]
A --> C[LinkedBlockingQueue]
A --> D[PriorityBlockingQueue]
A --> E[DelayQueue]
A --> F[SynchronousQueue]
B --> B1[固定大小餐厅]
C --> C1[无限制餐厅]
D --> D1[优先级餐厅]
E --> E1[定时取餐]
F --> F1[即点即取]
2. ArrayBlockingQueue - 像固定座位的餐厅 🏪
public class Restaurant {
// 创建一个容量为10的候餐区
private final BlockingQueue<Order> orderQueue = new ArrayBlockingQueue<>(10);
static class Order {
private final String customerName;
private final String dishName;
public Order(String customerName, String dishName) {
this.customerName = customerName;
this.dishName = dishName;
}
@Override
public String toString() {
return customerName + "的" + dishName;
}
}
// 服务员接单
public void takeOrder(Order order) {
try {
// 如果候餐区满了,服务员等待
orderQueue.put(order);
System.out.println("接到订单: " + order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 厨师做菜
public void cookOrder() {
try {
// 如果没有订单,厨师等待
Order order = orderQueue.take();
System.out.println("正在制作: " + order);
// 模拟做菜时间
Thread.sleep(1000);
System.out.println("完成订单: " + order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
// 创建服务员线程
new Thread(() -> {
for (int i = 1; i <= 20; i++) {
restaurant.takeOrder(new Order("顾客" + i, "菜品" + i));
}
}, "服务员").start();
// 创建多个厨师线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) {
restaurant.cookOrder();
}
}, "厨师" + (i + 1)).start();
}
}
}
3. LinkedBlockingQueue - 像无限座位的自助餐厅 🍱
public class BuffetRestaurant {
// 创建一个不限容量的排队队列
private final BlockingQueue<Customer> customerQueue = new LinkedBlockingQueue<>();
static class Customer {
String name;
public Customer(String name) {
this.name = name;
}
}
// 顾客排队
public void enterQueue(Customer customer) {
customerQueue.offer(customer);
System.out.println(customer.name + "加入队列");
}
// 取餐
public void serveCustomer() {
try {
Customer customer = customerQueue.take();
System.out.println("正在服务: " + customer.name);
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
4. PriorityBlockingQueue - 像VIP餐厅 🎩
public class VipRestaurant {
// 创建一个优先级队列,VIP优先服务
private final PriorityBlockingQueue<Order> orderQueue =
new PriorityBlockingQueue<>(10,
Comparator.comparingInt(o -> o.vipLevel));
static class Order implements Comparable<Order> {
String customerName;
int vipLevel; // VIP等级,数字越小优先级越高
public Order(String customerName, int vipLevel) {
this.customerName = customerName;
this.vipLevel = vipLevel;
}
@Override
public int compareTo(Order other) {
return Integer.compare(this.vipLevel, other.vipLevel);
}
}
}
5. DelayQueue - 像预约取餐系统 ⏰
public class TakeoutRestaurant {
// 创建一个延迟队列,用于定时取餐
private final DelayQueue<DelayedOrder> orderQueue = new DelayQueue<>();
static class DelayedOrder implements Delayed {
private final String orderInfo;
private final long readyTime;
public DelayedOrder(String orderInfo, long delayInMillis) {
this.orderInfo = orderInfo;
this.readyTime = System.currentTimeMillis() + delayInMillis;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(readyTime - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed other) {
return Long.compare(getDelay(TimeUnit.MILLISECONDS),
other.getDelay(TimeUnit.MILLISECONDS));
}
}
// 下预约订单
public void placeOrder(String orderInfo, long delayInMillis) {
orderQueue.put(new DelayedOrder(orderInfo, delayInMillis));
}
// 取餐服务
public void startDeliveryService() {
new Thread(() -> {
while (true) {
try {
DelayedOrder order = orderQueue.take();
System.out.println("订单已准备好: " + order.orderInfo);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "取餐服务").start();
}
}
6. SynchronousQueue - 像快餐窗口 🍔
public class FastFoodCounter {
// 创建一个同步队列,不存储订单,直接交付
private final SynchronousQueue<String> counter = new SynchronousQueue<>();
// 服务员下单
public void placeOrder(String order) {
try {
System.out.println("等待厨师接单: " + order);
counter.put(order);
System.out.println("厨师已接单: " + order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 厨师接单
public void processOrder() {
try {
String order = counter.take();
System.out.println("厨师开始制作: " + order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
7. 生产者-消费者模式示例
public class RestaurantSystem {
private final BlockingQueue<Order> orderQueue;
private final int cookCount;
private volatile boolean isOpen = true;
public RestaurantSystem(int queueSize, int cookCount) {
this.orderQueue = new ArrayBlockingQueue<>(queueSize);
this.cookCount = cookCount;
}
// 启动餐厅
public void start() {
// 启动服务员线程
new Thread(this::waiterWork, "服务员").start();
// 启动多个厨师线程
for (int i = 0; i < cookCount; i++) {
new Thread(this::cookWork, "厨师" + (i + 1)).start();
}
}
// 服务员工作
private void waiterWork() {
try {
int orderNum = 1;
while (isOpen) {
Order order = new Order("订单" + orderNum++);
orderQueue.put(order);
System.out.println("服务员接单: " + order);
Thread.sleep(500); // 模拟接单时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 厨师工作
private void cookWork() {
try {
while (isOpen || !orderQueue.isEmpty()) {
Order order = orderQueue.take();
System.out.println(Thread.currentThread().getName() +
" 正在制作: " + order);
Thread.sleep(1000); // 模拟制作时间
System.out.println(Thread.currentThread().getName() +
" 完成订单: " + order);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 关闭餐厅
public void close() {
isOpen = false;
}
}
8. 不同队列的特点对比
graph TB
A[BlockingQueue类型] --> B[ArrayBlockingQueue]
A --> C[LinkedBlockingQueue]
A --> D[PriorityBlockingQueue]
A --> E[DelayQueue]
A --> F[SynchronousQueue]
B --> B1[固定容量]
B --> B2[循环数组]
C --> C1[无限容量]
C --> C2[链表结构]
D --> D1[优先级排序]
D --> D2[堆实现]
E --> E1[延迟获取]
E --> E2[优先级队列]
F --> F1[零容量]
F --> F2[直接交付]
记住:
- ArrayBlockingQueue: 适合固定大小的场景
- LinkedBlockingQueue: 适合动态增长的场景
- PriorityBlockingQueue: 需要优先级处理的场景
- DelayQueue: 需要延时处理的场景
- SynchronousQueue: 需要即时交付的场景
选择合适的队列类型对提高系统性能很重要!
16. 线程安全的集合
- Vector: 线程安全的ArrayList
- Hashtable: 线程安全的HashMap
- ConcurrentHashMap: 分段锁实现的Map
- CopyOnWriteArrayList: 写时复制的List
- ConcurrentLinkedQueue: 非阻塞队列
让我用生动的例子来解释这些线程安全集合的实现原理。
1. Vector - 像带锁的储物柜 🔒
public class SafeLocker {
private Vector<String> items = new Vector<>();
public void addItem(String item) {
items.add(item); // synchronized
}
public String getItem(int index) {
return items.get(index); // synchronized
}
}
原理解释:
graph TD
A[Vector操作] --> B{synchronized锁}
B --> C[一次只允许一个线程访问]
B --> D[其他线程等待]
C --> E[完成操作]
E --> F[释放锁]
D --> B
就像一个带锁的储物柜:
- 每次只能一个人操作
- 操作时会锁门
- 其他人必须等待
- 性能较差(都要排队)
2. Hashtable - 像老式银行柜台 🏦
public class OldBank {
private Hashtable<String, Double> accounts = new Hashtable<>();
public void deposit(String account, Double amount) {
accounts.put(account, accounts.getOrDefault(account, 0.0) + amount);
}
public Double getBalance(String account) {
return accounts.get(account);
}
}
原理解释:
graph LR
A[客户请求] --> B{全局锁}
B --> C[账户操作]
C --> D[释放锁]
D --> E[下一个客户]
就像老式银行:
- 所有操作都要排队
- 一次只能服务一个客户
- 其他客户必须等待
- 效率低下
3. ConcurrentHashMap - 像现代银行 🏢
public class ModernBank {
private ConcurrentHashMap<String, Double> accounts = new ConcurrentHashMap<>();
public void deposit(String account, Double amount) {
accounts.compute(account, (k, v) -> (v == null ? 0 : v) + amount);
}
public Double getBalance(String account) {
return accounts.get(account);
}
}
原理解释:
graph TD
A[ConcurrentHashMap] --> B[分段锁Segment]
B --> C[Segment 1]
B --> D[Segment 2]
B --> E[Segment 3]
C --> F[多个Entry]
D --> G[多个Entry]
E --> H[多个Entry]
就像现代银行:
- 多个柜台同时服务
- 每个柜台独立工作
- 不同柜台互不影响
- 效率更高
4. CopyOnWriteArrayList - 像共享文档系统 📄
public class DocumentSystem {
private CopyOnWriteArrayList<String> documents = new CopyOnWriteArrayList<>();
public void addDocument(String doc) {
documents.add(doc); // 创建新副本
}
public void readDocuments() {
// 读取不需要锁
for (String doc : documents) {
System.out.println(doc);
}
}
}
原理解释:
sequenceDiagram
participant Writer as 写线程
participant Original as 原始数组
participant Copy as 数组副本
participant Reader as 读线程
Writer->>Original: 请求写入
Writer->>Copy: 创建副本
Writer->>Copy: 修改副本
Writer->>Original: 替换原始数组
Reader->>Original: 读取(不受影响)
就像共享文档系统:
- 修改时创建新副本
- 读取时使用原件
- 修改完成后替换
- 适合读多写少的场景
5. ConcurrentLinkedQueue - 像自助餐厅 🍽️
public class ModernBuffet {
private ConcurrentLinkedQueue<String> dishes = new ConcurrentLinkedQueue<>();
public void addDish(String dish) {
dishes.offer(dish); // 无锁添加
}
public String takeDish() {
return dishes.poll(); // 无锁获取
}
}
原理解释:
graph LR
A[头节点] --> B[节点1]
B --> C[节点2]
C --> D[节点3]
D --> E[尾节点]
F[线程1] -.-> B
G[线程2] -.-> D
H[线程3] -.-> C
就像自助餐厅:
- 多人可同时取餐
- 使用CAS操作保证安全
- 无需加锁,效率高
- 适合高并发场景
6. 综合比较示例
public class CollectionPerformanceTest {
private static final int THREAD_COUNT = 100;
private static final int OPERATION_COUNT = 10000;
public static void main(String[] args) throws InterruptedException {
// 测试不同集合的性能
testCollection(new Vector<>(), "Vector");
testCollection(new CopyOnWriteArrayList<>(), "CopyOnWriteArrayList");
testMap(new Hashtable<>(), "Hashtable");
testMap(new ConcurrentHashMap<>(), "ConcurrentHashMap");
testQueue(new ConcurrentLinkedQueue<>(), "ConcurrentLinkedQueue");
}
private static void testCollection(List<Integer> list, String name) {
long start = System.currentTimeMillis();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
list.add(j);
list.get(j % (j + 1));
}
latch.countDown();
}).start();
}
try {
latch.await();
System.out.println(name + " 耗时: " +
(System.currentTimeMillis() - start) + "ms");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
7. 各集合类型的特点对比
graph TB
A[线程安全集合] --> B[Vector]
A --> C[Hashtable]
A --> D[ConcurrentHashMap]
A --> E[CopyOnWriteArrayList]
A --> F[ConcurrentLinkedQueue]
B --> B1[synchronized]
B --> B2[全局锁]
C --> C1[synchronized]
C --> C2[全局锁]
D --> D1[分段锁]
D --> D2[CAS操作]
E --> E1[写时复制]
E --> E2[读写分离]
F --> F1[无锁]
F --> F2[CAS操作]
8. 使用场景建议
| 集合类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Vector | 简单同步需求 | 实现简单 | 性能差 |
| Hashtable | 简单同步需求 | 实现简单 | 性能差 |
| ConcurrentHashMap | 高并发读写 | 性能好 | 内存占用较大 |
| CopyOnWriteArrayList | 读多写少 | 读性能好 | 写性能差 |
| ConcurrentLinkedQueue | 高并发队列 | 性能好 | 不保证即时一致性 |
9. 最佳实践示例
public class ThreadSafeCollectionExample {
// 用户信息缓存
private final ConcurrentHashMap<String, UserInfo> userCache =
new ConcurrentHashMap<>();
// 操作日志
private final CopyOnWriteArrayList<String> operationLogs =
new CopyOnWriteArrayList<>();
// 任务队列
private final ConcurrentLinkedQueue<Task> taskQueue =
new ConcurrentLinkedQueue<>();
public void processUser(String userId, UserInfo info) {
// 更新用户信息
userCache.put(userId, info);
// 记录操作日志
operationLogs.add("Updated user: " + userId);
// 添加后续任务
taskQueue.offer(new Task("process-" + userId));
}
static class UserInfo {
String name;
int age;
}
static class Task {
String taskId;
Task(String taskId) {
this.taskId = taskId;
}
}
}
记住:
- Vector/Hashtable: 简单场景,不追求性能时使用
- ConcurrentHashMap: 需要高并发读写时使用
- CopyOnWriteArrayList: 读多写少的场景使用
- ConcurrentLinkedQueue: 高并发队列操作时使用
选择合适的线程安全集合可以大大提高系统性能!
17. 线程安全的集合底层实现
让我详细解析每个线程安全集合的底层实现原理。
1. Vector 底层实现 - 数组 + synchronized 🔒
public class Vector<E> {
// 底层使用数组存储
protected Object[] elementData;
// 实际元素个数
protected int elementCount;
// 所有方法都使用synchronized修饰
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
}
graph TD
A[Vector] --> B[Object数组]
B --> C[synchronized方法]
C --> D[全局锁]
D --> E[性能较差]
2. Hashtable 底层实现 - 数组 + 链表 + synchronized 🏦
public class Hashtable<K,V> {
// 底层使用Entry数组
private Entry<?,?>[] table;
// Entry节点,形成链表
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
}
// 所有方法都使用synchronized修饰
public synchronized V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
// ... 哈希计算和冲突处理
}
}
graph TD
A[Hashtable] --> B[Entry数组]
B --> C[链表结构]
C --> D[synchronized方法]
D --> E[全局锁]
3. ConcurrentHashMap 底层实现 - Node数组 + CAS + synchronized 🏢
public class ConcurrentHashMap<K,V> {
// Node数组(表)
transient volatile Node<K,V>[] table;
// Node节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
// put操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// CAS操作确保线程安全
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
// 其他情况使用synchronized锁定节点
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
// ... 处理冲突
}
}
}
}
}
}
graph TD
A[ConcurrentHashMap] --> B[Node数组]
B --> C[CAS操作]
B --> D[synchronized锁]
C --> E[无锁操作]
D --> F[细粒度锁]
E --> G[高并发性能]
F --> G
4. CopyOnWriteArrayList 底层实现 - 数组副本 + ReentrantLock 📄
public class CopyOnWriteArrayList<E> {
// 存储数组
private transient volatile Object[] array;
// 独占锁
final transient ReentrantLock lock = new ReentrantLock();
// 添加元素
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 创建新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
// 获取元素(无锁)
public E get(int index) {
return get(getArray(), index);
}
}
sequenceDiagram
participant Thread1 as 写线程
participant Array as 原数组
participant Lock as ReentrantLock
participant NewArray as 新数组
participant Thread2 as 读线程
Thread1->>Lock: 获取锁
Thread1->>Array: 复制数组
Thread1->>NewArray: 修改新数组
Thread1->>Array: 替换原数组
Thread1->>Lock: 释放锁
Thread2->>Array: 直接读取(无锁)
5. ConcurrentLinkedQueue 底层实现 - CAS + 链表 🔄
public class ConcurrentLinkedQueue<E> {
// 节点类
private static class Node<E> {
volatile E item;
volatile Node<E> next;
}
// 头尾节点
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
// 入队操作
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// CAS设置next引用
if (p.casNext(null, newNode)) {
// 更新tail
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
}
graph LR
A[头节点] -->|CAS| B[节点1]
B -->|CAS| C[节点2]
C -->|CAS| D[节点3]
D -->|CAS| E[尾节点]
6. 性能对比
public class CollectionPerformanceTest {
private static final int THREAD_COUNT = 10;
private static final int OPERATION_COUNT = 100000;
public static void main(String[] args) throws Exception {
// 测试各种集合的性能
testCollection(new Vector<>(), "Vector");
testCollection(new CopyOnWriteArrayList<>(), "CopyOnWriteArrayList");
testMap(new Hashtable<>(), "Hashtable");
testMap(new ConcurrentHashMap<>(), "ConcurrentHashMap");
testQueue(new ConcurrentLinkedQueue<>(), "ConcurrentLinkedQueue");
}
private static void testCollection(List<Integer> list, String name) {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
list.add(j);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(name + " 耗时: " +
(System.currentTimeMillis() - startTime) + "ms");
}
}
7. 各集合的特点总结
graph TB
A[线程安全集合] --> B[实现方式]
B --> C[锁类型]
B --> D[数据结构]
B --> E[性能特点]
C --> C1[synchronized全局锁]
C --> C2[分段锁]
C --> C3[CAS无锁]
D --> D1[数组]
D --> D2[链表]
D --> D3[数组+链表]
E --> E1[读写性能]
E --> E2[内存消耗]
E --> E3[并发度]
8. 选择建议
| 集合类型 | 底层实现 | 线程安全实现 | 适用场景 |
|---|---|---|---|
| Vector | 数组 | synchronized | 简单同步需求 |
| Hashtable | 数组+链表 | synchronized | 简单同步需求 |
| ConcurrentHashMap | 数组+链表 | CAS+synchronized | 高并发读写 |
| CopyOnWriteArrayList | 数组副本 | ReentrantLock | 读多写少 |
| ConcurrentLinkedQueue | 链表 | CAS | 高并发队列 |
记住:
- Vector/Hashtable:使用synchronized,性能较差
- ConcurrentHashMap:使用分段锁+CAS,性能好
- CopyOnWriteArrayList:写时复制,读性能好
- ConcurrentLinkedQueue:CAS无锁,高并发性能好
选择时要根据具体场景权衡性能和内存消耗!
18. Atomic类
原子类特点:
- 基于CAS操作
- 保证原子性
- 性能比synchronized好
缺点:
- ABA问题
- 循环时间长开销大
- 只能保证一个共享变量的原子操作
让我用一个生动的例子来解释原子类:
/*
想象一个银行柜台场景:
普通变量 = 手工记账
原子类 = 电子记账系统
多个柜员同时操作:
- 手工记账可能出错(数据不一致)
- 电子系统永远准确(原子操作)
*/
class BankExample {
// 手工记账(普通变量)
private var manualBalance = 1000
// 电子系统(原子类)
private val atomicBalance = AtomicInteger(1000)
fun demonstrate() {
// 模拟多个柜员同时操作
repeat(3) { tellerId ->
thread {
// 手工记账
manualBalance += 100
// 电子系统
atomicBalance.addAndGet(100)
}
}
Thread.sleep(1000)
println("手工记账余额: $manualBalance") // 可能不是1300
println("电子系统余额: ${atomicBalance.get()}") // 一定是1300
}
}
具体场景演示:
/*
想象售票窗口场景:
- 多个窗口同时卖票
- 需要保证不超卖
*/
class TicketSystem {
// 普通计数(不安全)
private var normalTickets = 100
// 原子计数(安全)
private val atomicTickets = AtomicInteger(100)
// 模拟售票
fun sellTickets() {
// 开启5个售票窗口
repeat(5) { windowId ->
thread {
repeat(10) {
// 普通售票(可能出问题)
if (normalTickets > 0) {
normalTickets-- // 可能超卖
}
// 原子售票(绝对安全)
while (true) {
val current = atomicTickets.get()
if (current <= 0) break
// compareAndSet 就像"看票-卖票"是一气呵成的
if (atomicTickets.compareAndSet(current, current - 1)) {
println("窗口$windowId 卖出一张票,剩余${atomicTickets.get()}张")
break
}
}
}
}
}
}
}
CAS操作形象解释:
/*
想象超市结账场景:
普通操作:
1. 收银员看到价格100元
2. 找零时发现价格已被改成200元
3. 按照100元找零就出错了
CAS操作:
1. 收银员看到价格100元
2. 在收款前再次确认价格还是100元
3. 如果价格变了,重新开始整个过程
*/
class CashierExample {
private val price = AtomicInteger(100)
fun checkout() {
while (true) {
val currentPrice = price.get() // 看价格
val newPrice = currentPrice - 50 // 计算折扣
// 确认价格没变并更新
if (price.compareAndSet(currentPrice, newPrice)) {
println("成功修改价格为: $newPrice")
break
} else {
println("价格已被其他收银员修改,重试")
}
}
}
}
常见原子操作:
class AtomicOperations {
private val counter = AtomicInteger(0)
fun examples() {
// 1. 增加并获取 (像电子计数器)
counter.incrementAndGet() // ++i
counter.getAndIncrement() // i++
// 2. 更新值 (像电子价签)
counter.set(100)
// 3. 获取当前值 (像查看显示屏)
val current = counter.get()
// 4. 比较并设置 (像确认后更新)
counter.compareAndSet(100, 200)
// 5. 增加指定值 (像批量加减)
counter.addAndGet(50)
}
}
实际应用场景:
class RealWorldExamples {
// 1. 计数器(像点击量统计)
private val pageViews = AtomicLong(0)
// 2. 状态标记(像开关状态)
private val initialized = AtomicBoolean(false)
// 3. 库存管理(像商品库存)
private val stock = AtomicInteger(100)
fun operate() {
// 记录访问量
pageViews.incrementAndGet()
// 初始化一次
if (initialized.compareAndSet(false, true)) {
println("首次初始化")
}
// 减少库存
if (stock.decrementAndGet() >= 0) {
println("成功扣减库存")
} else {
println("库存不足")
}
}
}
总结:
- 原子类就像电子系统,保证操作的准确性
- CAS像是"看-改"的一气呵成操作
- 适合简单的计数、状态管理场景
- 不需要加锁就能保证线程安全
- 性能通常比锁要好
使用建议:
- 简单计数用AtomicInteger
- 状态标记用AtomicBoolean
- 对象引用用AtomicReference
- 需要多个操作原子性时用锁
- 高并发场景优先考虑原子类
19. ThreadLocal使用场景
使用场景:
- 数据库连接
- Session管理
- 用户身份信息传递
- 事务上下文
与Synchronized比较:
- ThreadLocal: 线程隔离,各线程独立
- Synchronized: 线程同步,保证互斥
这些是Java多线程编程中最常见的面试题及其详细解答。每个主题都很重要,建议深入理解原理和使用场景。
20.请详细说明下CAS原理和应用
让我用生动的例子来解释CAS(Compare And Swap)操作:
1. 生活中的CAS例子
场景一:排队买奶茶
sequenceDiagram
participant C as 顾客
participant S as 奶茶店
C->>S: 看到剩余10杯奶茶
C->>S: 想买1杯
C->>S: 确认还是10杯才买
Note over S: 如果还是10杯:成功购买
Note over S: 如果不是10杯:重新查看
就像你去买奶茶:
- 看到还剩10杯(获取当前值)
- 你想买1杯(准备更新)
- 付款前再确认还是10杯(比较并交换)
- 如果还是10杯:成功买到
- 如果变成9杯:重新确认数量
2. 代码中的CAS
简单的CAS操作:
public class CASExample {
private AtomicInteger tickets = new AtomicInteger(100);
public boolean buyTicket() {
while (true) {
// 获取当前票数
int current = tickets.get();
// 如果没票了,返回false
if (current <= 0) {
return false;
}
// 尝试更新票数,只有当前值等于current时才会成功
if (tickets.compareAndSet(current, current - 1)) {
return true; // 购票成功
}
// 如果失败,说明有其他人先买了,继续循环尝试
}
}
}
3. 形象的图解
CAS操作流程:
graph TD
A[开始] --> B[读取当前值]
B --> C{值是否符合预期?}
C -->|是| D[更新成功]
C -->|否| E[更新失败]
E --> B
4. 真实场景示例
场景:电影院售票系统
public class Cinema {
private AtomicInteger[] seats; // 每个座位的状态
public Cinema(int seatCount) {
seats = new AtomicInteger[seatCount];
for (int i = 0; i < seatCount; i++) {
seats[i] = new AtomicInteger(0); // 0表示未售出
}
}
public boolean bookSeat(int seatNumber) {
// 获取座位
AtomicInteger seat = seats[seatNumber];
// 尝试预订座位
while (true) {
int current = seat.get();
// 座位已被预订
if (current == 1) {
System.out.println("座位" + seatNumber + "已被预订");
return false;
}
// CAS操作:尝试将座位状态从0改为1
if (seat.compareAndSet(0, 1)) {
System.out.println("成功预订座位" + seatNumber);
return true;
}
// 如果CAS失败,说明其他人刚刚预订了这个座位
System.out.println("座位" + seatNumber + "预订失败,重试中...");
}
}
}
// 使用示例
public class BookingDemo {
public static void main(String[] args) {
Cinema cinema = new Cinema(10);
// 多个线程同时订票
new Thread(() -> cinema.bookSeat(5)).start();
new Thread(() -> cinema.bookSeat(5)).start();
new Thread(() -> cinema.bookSeat(5)).start();
}
}
5. CAS的优缺点
优点图解:
graph LR
A[CAS优点] --> B[无锁]
A --> C[高性能]
A --> D[不会死锁]
B --> B1[不需要线程阻塞]
C --> C1[适合高并发]
D --> D1[更安全]
缺点示例:
// ABA问题示例
public class ABAExample {
private AtomicReference<Integer> value =
new AtomicReference<>(100);
public void update() {
Integer oldValue = value.get(); // 获取值100
// 其他线程执行了 100 -> 101 -> 100 的操作
// 当前线程无法感知这个变化
value.compareAndSet(oldValue, 200); // 仍然成功
}
}
// 解决ABA问题
public class ABAFixed {
private AtomicStampedReference<Integer> value =
new AtomicStampedReference<>(100, 0);
public void update() {
int stamp = value.getStamp();
Integer oldValue = value.getReference();
// 检查值和版本号
value.compareAndSet(oldValue, 200, stamp, stamp + 1);
}
}
6. 实际应用场景
1. 计数器:
public class ConcurrentCounter {
private AtomicLong count = new AtomicLong(0);
public void increment() {
while (true) {
long current = count.get();
if (count.compareAndSet(current, current + 1)) {
break;
}
// 如果失败,说明其他线程修改了值,重试
}
}
}
2. 缓存更新:
public class CacheUpdater {
private AtomicReference<Map<String, String>> cache =
new AtomicReference<>(new HashMap<>());
public void updateCache(String key, String value) {
while (true) {
Map<String, String> current = cache.get();
Map<String, String> newMap = new HashMap<>(current);
newMap.put(key, value);
if (cache.compareAndSet(current, newMap)) {
break;
}
}
}
}
7. 总结
CAS就像是一个"乐观"的操作:
- 先看看现在的值是什么
- 计算新的值
- 在更新时确认原来的值没变
- 没变:更新成功
- 变了:重试
优点:
- 不需要加锁,性能好
- 适合并发场景
- 不会死锁
缺点:
- 可能会自旋(一直重试)
- 存在ABA问题
- 只能保证单个变量的原子性
使用建议:
- 适合简单的原子操作
- 需要考虑ABA问题
- 高并发场景下要考虑自旋开销
通过这些例子,我们可以看到CAS是一种非常巧妙的无锁并发机制,在正确的场景下使用可以带来很好的性能提升。
让我用一个生动的例子来解释原子类:
/*
想象一个银行柜台场景:
普通变量 = 手工记账
原子类 = 电子记账系统
多个柜员同时操作:
- 手工记账可能出错(数据不一致)
- 电子系统永远准确(原子操作)
*/
class BankExample {
// 手工记账(普通变量)
private var manualBalance = 1000
// 电子系统(原子类)
private val atomicBalance = AtomicInteger(1000)
fun demonstrate() {
// 模拟多个柜员同时操作
repeat(3) { tellerId ->
thread {
// 手工记账
manualBalance += 100
// 电子系统
atomicBalance.addAndGet(100)
}
}
Thread.sleep(1000)
println("手工记账余额: $manualBalance") // 可能不是1300
println("电子系统余额: ${atomicBalance.get()}") // 一定是1300
}
}
具体场景演示:
/*
想象售票窗口场景:
- 多个窗口同时卖票
- 需要保证不超卖
*/
class TicketSystem {
// 普通计数(不安全)
private var normalTickets = 100
// 原子计数(安全)
private val atomicTickets = AtomicInteger(100)
// 模拟售票
fun sellTickets() {
// 开启5个售票窗口
repeat(5) { windowId ->
thread {
repeat(10) {
// 普通售票(可能出问题)
if (normalTickets > 0) {
normalTickets-- // 可能超卖
}
// 原子售票(绝对安全)
while (true) {
val current = atomicTickets.get()
if (current <= 0) break
// compareAndSet 就像"看票-卖票"是一气呵成的
if (atomicTickets.compareAndSet(current, current - 1)) {
println("窗口$windowId 卖出一张票,剩余${atomicTickets.get()}张")
break
}
}
}
}
}
}
}
CAS操作形象解释:
/*
想象超市结账场景:
普通操作:
1. 收银员看到价格100元
2. 找零时发现价格已被改成200元
3. 按照100元找零就出错了
CAS操作:
1. 收银员看到价格100元
2. 在收款前再次确认价格还是100元
3. 如果价格变了,重新开始整个过程
*/
class CashierExample {
private val price = AtomicInteger(100)
fun checkout() {
while (true) {
val currentPrice = price.get() // 看价格
val newPrice = currentPrice - 50 // 计算折扣
// 确认价格没变并更新
if (price.compareAndSet(currentPrice, newPrice)) {
println("成功修改价格为: $newPrice")
break
} else {
println("价格已被其他收银员修改,重试")
}
}
}
}
常见原子操作:
class AtomicOperations {
private val counter = AtomicInteger(0)
fun examples() {
// 1. 增加并获取 (像电子计数器)
counter.incrementAndGet() // ++i
counter.getAndIncrement() // i++
// 2. 更新值 (像电子价签)
counter.set(100)
// 3. 获取当前值 (像查看显示屏)
val current = counter.get()
// 4. 比较并设置 (像确认后更新)
counter.compareAndSet(100, 200)
// 5. 增加指定值 (像批量加减)
counter.addAndGet(50)
}
}
实际应用场景:
class RealWorldExamples {
// 1. 计数器(像点击量统计)
private val pageViews = AtomicLong(0)
// 2. 状态标记(像开关状态)
private val initialized = AtomicBoolean(false)
// 3. 库存管理(像商品库存)
private val stock = AtomicInteger(100)
fun operate() {
// 记录访问量
pageViews.incrementAndGet()
// 初始化一次
if (initialized.compareAndSet(false, true)) {
println("首次初始化")
}
// 减少库存
if (stock.decrementAndGet() >= 0) {
println("成功扣减库存")
} else {
println("库存不足")
}
}
}
总结:
- 原子类就像电子系统,保证操作的准确性
- CAS像是"看-改"的一气呵成操作
- 适合简单的计数、状态管理场景
- 不需要加锁就能保证线程安全
- 性能通常比锁要好
使用建议:
- 简单计数用AtomicInteger
- 状态标记用AtomicBoolean
- 对象引用用AtomicReference
- 需要多个操作原子性时用锁
- 高并发场景优先考虑原子类
21.CAS和synchronized的区别
让我用通俗易懂的方式来解释CAS和synchronized的区别:
1. CAS(Compare And Swap)
通俗理解: 想象你在修改一个数字,就像在玩"抢座位"游戏:
- 看到座位是空的(获取当前值)
- 准备坐下(准备修改)
- 坐下前再次确认座位还是空的(比较并交换)
// CAS操作示例
public class CASExample {
private AtomicInteger value = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = value.get(); // 获取当前值
newValue = oldValue + 1; // 计算新值
} while (!value.compareAndSet(oldValue, newValue)); // CAS操作
}
}
图解CAS过程:
sequenceDiagram
participant T1 as 线程1
participant M as 内存
participant T2 as 线程2
T1->>M: 读取值A
T2->>M: 读取值A
T1->>M: CAS(A→B) 成功
T2->>M: CAS(A→B) 失败
T2->>M: 重新读取值B
T2->>M: CAS(B→C) 成功
2. synchronized
通俗理解: 像是给房间上锁:
- 进入房间前先上锁(其他人进不来)
- 做完事情后开锁(其他人才能进)
public class SynchronizedExample {
private int value = 0;
public synchronized void increment() {
value++; // 直接修改,因为已经上锁了
}
}
图解synchronized过程:
sequenceDiagram
participant T1 as 线程1
participant L as 锁
participant T2 as 线程2
T1->>L: 获取锁
T2->>L: 尝试获取锁(等待)
T1->>L: 执行操作
T1->>L: 释放锁
T2->>L: 获取锁
T2->>L: 执行操作
T2->>L: 释放锁
3. 主要区别
graph TD
A[CAS vs Synchronized] --> B[并发策略]
A --> C[性能影响]
A --> D[使用场景]
A --> E[实现机制]
B --> B1[CAS: 乐观锁]
B --> B2[Synchronized: 悲观锁]
C --> C1[CAS: 无阻塞]
C --> C2[Synchronized: 可能阻塞]
D --> D1[CAS: 简单原子操作]
D --> D2[Synchronized: 复杂同步操作]
E --> E1[CAS: 硬件级别]
E --> E2[Synchronized: 操作系统级别]
4. 实际应用示例
1. CAS适用场景:
// 计数器场景
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 使用CAS
}
public int getCount() {
return count.get();
}
}
2. synchronized适用场景:
// 复杂业务逻辑场景
public class BankAccount {
private double balance;
public synchronized void transfer(BankAccount target, double amount) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
}
}
5. 在ConcurrentHashMap中的应用
public class ConcurrentHashMap<K,V> {
// CAS操作:插入空桶
final V putVal(K key, V value, boolean onlyIfAbsent) {
if ((p = tabAt(tab, i = (n - 1) & hash)) == null) {
// 如果桶是空的,用CAS操作插入
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
return null;
}
// 如果发生冲突,使用synchronized
else {
synchronized (p) {
// 处理冲突
}
}
}
}
6. 性能比较
graph LR
A[性能比较] --> B[CAS]
A --> C[Synchronized]
B --> B1[优点]
B --> B2[缺点]
C --> C1[优点]
C --> C2[缺点]
B1 --> B11[无锁开销]
B1 --> B12[适合简单操作]
B2 --> B21[可能自旋]
B2 --> B22[ABA问题]
C1 --> C11[实现简单]
C1 --> C12[适合复杂操作]
C2 --> C21[有锁开销]
C2 --> C22[可能阻塞]
7. 使用建议
- 选择CAS的场景:
// 简单的原子操作
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
// 状态更新
AtomicReference<State> state = new AtomicReference<>(State.INIT);
state.compareAndSet(State.INIT, State.RUNNING);
- 选择synchronized的场景:
// 复杂的业务逻辑
synchronized void complexOperation() {
// 多个步骤的操作
step1();
step2();
step3();
}
// 需要保护多个资源
synchronized void transferMoney(Account from, Account to, double amount) {
// 涉及多个对象的操作
}
总结:
- CAS适合简单的原子操作,性能更好,但可能出现ABA问题
- synchronized适合复杂的同步操作,实现简单,但性能较差
- ConcurrentHashMap巧妙地结合了两者的优点:
- 用CAS处理无冲突情况
- 用synchronized处理冲突情况
这种组合使用的方式充分发挥了两种机制的优势,是一种非常优秀的设计。
22.volatile的原理和作用
让我详细解释volatile的原理和作用:
3. volatile的基本概念
通俗理解: 想象成"告示牌"机制:
- 所有人看到的都是最新的公告
- 一个人更新了公告,其他人立即能看到
- 但不能保证多人同时更新公告的正确性
2. volatile的三大特性
graph TD
A[volatile特性] --> B[可见性]
A --> C[有序性]
A --> D[单次写入原子性]
B --> B1[所有线程立即看到最新值]
C --> C1[禁止指令重排]
D --> D1[单次读/写操作原子性]
3. 实际应用示例
1. 状态标志:
public class TaskRunner {
private volatile boolean running = false;
public void start() {
running = true; // 其他线程立即可见
while(running) {
// 执行任务
}
}
public void stop() {
running = false; // 其他线程立即可见
}
}
2. 双重检查锁定:
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
4. volatile vs synchronized vs CAS
graph TB
A[并发控制机制] --> B[volatile]
A --> C[synchronized]
A --> D[CAS]
B --> B1[保证可见性]
B --> B2[不保证原子性]
B --> B3[性能最好]
C --> C1[保证可见性]
C --> C2[保证原子性]
C --> C3[性能最差]
D --> D1[保证可见性]
D --> D2[保证原子性]
D --> D3[性能居中]
5. 在ConcurrentHashMap中的应用
public class ConcurrentHashMap<K,V> {
// volatile数组,确保所有线程看到最新的table
private transient volatile Node<K,V>[] table;
// volatile变量,确保size操作的可见性
private transient volatile int sizeCtl;
static class Node<K,V> {
final int hash;
final K key;
// volatile值,确保修改对所有线程立即可见
volatile V val;
volatile Node<K,V> next;
}
}
6. volatile读取流程
sequenceDiagram
participant T1 as 线程1
participant M as 主内存
participant T2 as 线程2
T1->>M: 写入volatile变量
Note over M: 强制刷新到主内存
M->>T2: 通知其他线程
T2->>M: 从主内存读取最新值
7. 使用场景对比
1. 适合使用volatile的场景:
public class ConfigManager {
private volatile String config;
// 一个线程写,多个线程读的场景
public void updateConfig(String newConfig) {
config = newConfig;
}
public String getConfig() {
return config;
}
}
2. 不适合使用volatile的场景:
public class Counter {
private volatile int count; // 不适合
// 多线程计数场景
public void increment() {
count++; // 非原子操作
}
}
8. 性能考虑
// 1. 读取性能
public class VolatileRead {
private volatile int value;
public int getValue() {
return value; // 直接读取,性能好
}
}
// 2. 写入性能
public class VolatileWrite {
private volatile int value;
public void setValue(int value) {
this.value = value; // 需要内存屏障,性能略差
}
}
9. 最佳实践
- 正确使用场景:
// 状态标志
public class ServiceManager {
private volatile boolean isRunning;
public void start() {
isRunning = true;
}
public void stop() {
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}
- 组合使用:
public class CombinedExample {
private volatile long count = 0;
// 需要原子性时配合synchronized使用
public synchronized void increment() {
count++;
}
// 只读操作直接使用volatile即可
public long getCount() {
return count;
}
}
10. 总结
volatile特别适合以下场景:
- 一写多读的场景
- 状态标志
- 双重检查锁定
不适合的场景:
- 需要原子性的操作
- 复合操作
- 多线程写入的场景
在ConcurrentHashMap中:
- volatile确保了数据的可见性
- 配合CAS和synchronized实现完整的并发控制
- 是实现高性能并发的重要组成部分
理解volatile的特性和适用场景,对于编写高性能的并发程序非常重要。在ConcurrentHashMap中,它与CAS和synchronized一起,形成了完整的并发控制机制。