
JAVA并发
java集合
HashMap 为什么线程不安全?
多线程下会出现数据覆盖问题,put方法的步骤是先根据key计算出hash值,得到索引,找到该元素在数组中存储的下标,如果对应的下标没有值,则直接put,如果有则覆盖。 比如两个线程A,B,元素hash冲突,插入同一个下标,就会导致值覆盖问题。
AQS
AQS全称是AbstractQueuedSynchronizer,AQS是多线程同步器,它是J.U.C包中多个组件的底层实现,如Lock、CountDownLatch、Semaphore等都用到了AQS。
从本质上来说,AQS提供了两种锁机制,分别是排它锁和共享锁。
排它锁就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如Lock中的ReentrantLock重入锁实现就是用到了AQS中的排它锁功能。
共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch和Semaphore都是用到了AQS中的共享锁功能。
CAS
CAS(Compare-And-Swap),即比较并交换,是一种无锁的、乐观的并发原子操作。它保证了一个线程在更新一个变量时,只有当变量的预期值和内存中的实际值相同时,才会将新值写入。
Java中主要通过 sun.misc.Unsafe
类提供的底层CAS方法(JVM内部使用)来实现。开发者最常接触的是 java.util.concurrent.atomic
包下的原子类,
例如:
- AtomicInteger
- AtomicLong
- AtomicReference
示例:AtomicInteger
的 incrementAndGet()
java
AtomicInteger count = new AtomicInteger(0);
public void safeIncrement() {
// 内部基于CAS实现,即使多线程调用也安全
count.incrementAndGet();
}
它的内部实现类似于一个自旋循环:
java
public final int incrementAndGet() {
for (;;) { // 自旋
int current = get(); // 获取当前值 A
int next = current + 1; // 计算新值 B
if (compareAndSet(current, next)) // 核心CAS操作
return next; // 成功则返回
} // 失败则循环重试
}
优点:
- 高性能:避免了重量级锁(如synchronized)带来的线程阻塞、上下文切换的开销,在竞争不激烈的情况下性能远超加锁。
- 无死锁:由于是乐观重试,不存在死锁问题。
缺点:
- ABA问题:一个值初始是A,中途被改为B,后又改回A。CAS检查时会误以为它没变,从而成功操作。解决方案是使用带版本号的原子引用类 AtomicStampedReference。
- 循环时间长开销大:在高竞争环境下,如果线程一直重试,会空耗CPU资源。
ABA 问题
解决 ABA 问题的一种方法是使用带版本号的 CAS,也称为双重 CAS(Double CAS)或者版本号 CAS。具体来说,每次进行 CAS 操作时,不仅需要比较要修改的内存地址的值与期望的值是否相等,还需要比较这个内存地址的版本号是否与期望的版本号相等。如果相等,才进行修改操作。这样,在修改后的值后面追加上一个版本号,即使变量的值从 A 变成了 B 再变成了 A,版本号也会发生变化,从而避免了误判。
以下是一个使用 AtomicStampedReference 来解决 ABA 问题的示例代码:
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
public static void main(String[] args) throws InterruptedException {
System.out.println("初始值:" + atomicStampedRef.getReference() + ",版本号:" + atomicStampedRef.getStamp());
// 线程 1 先执行一次 CAS 操作,期望值为 1,新值为 2,版本号为 0
Thread thread1 = new Thread(() -> {
int stamp = atomicStampedRef.getStamp();
atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1);
});
// 线程 2 先 sleep 1 秒,让线程 1 先执行一次 CAS 操作,然后再执行一次 CAS 操作,期望值为 2,新值为 1,版本号为 1
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = atomicStampedRef.getStamp();
atomicStampedRef.compareAndSet(2, 1, stamp, stamp + 1);
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终值:" + atomicStampedRef.getReference() + ",版本号:" + atomicStampedRef.getStamp());
}
}
以上程序的执行结果为:
初始值:1,版本号:0
最终值:1,版本号:2