文章目录
一、CAS
CAS(Compare And Swap),即比较和交换。
CAS是一条CPU指令,即该指令是原子的,可以完成比较和交换操作。Java中,一般不会直接使用CAS,而是使用基于CAS封装好的类。
java
//CAS的伪代码
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
CAS的典型应用:
- 实现原子类
- 实现自旋锁
1.1 实现原子类
标准库中,提供了一系列的"原子"类,即基于CAS实现的内容;

Java不支持运算符重载,因此对于封装CAS的类对象,不能用++/--来直接操作 ;

java
//通过原子类保证线程安全
private static AtomicInteger ret=new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for(int i=0;i<50000;i++){
ret.getAndIncrement();
// getAndIncrement: i++
// incrementAndGet: ++i
// getAndDecrement: i--
// getAndDecrement: --i
});
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++){
ret.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(ret);
}
1.2 实现自旋锁
java
//简化版的自旋锁伪代码
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就⾃旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}

1.3 CAS的ABA问题
在 CAS(Compare-And-Swap)操作中,通常会先比较内存中的当前值与期望值是否相等:
- 如果相等,说明在此期间没有其他线程修改该变量,就执行交换操作;
- 如果不相等,说明有其他线程"插队"修改过该变量,当前线程需要进行重试。
这种判断机制本质上是只比较前后两个时刻的值是否相同。
ABA 问题正是由此产生的:如果一个变量的值从 A → B → A,那么在 CAS 比较时,发现当前值仍然是 A,就会误认为变量"从未被修改过",从而导致 CAS 成功。但实际上,该变量已经被其他线程修改过,可能带来逻辑错误。
即ABA 问题的根本原因在于:CAS 只能判断"值是否相等",而无法判断"值是否被修改过"。
虽然CAS存在这样的问题,但是ABA问题不一定会导致bug,或者说其导致bug的概率实际是非常低的。
ABA问题的解决方法:
- 引入新的值(版本号),该值只能加,不能减,每次修改,都让该值+1
二、哈希表
在多线程中,HashMap是线程不安全的。Java中,提供了两个线程安全的集合类。
1.HashTable

如上,HashTable就是将核心方法使用synchronized进行修饰 ,即将核心用法用this进行加锁。这意味着HashTable只有一把锁,任何的读写操作都可能需要这把锁,因此发生冲突的概率非常大。
2.ConcurrentHashMap
ConcurrentHashMap对HashTable存在的问题进行了一定的优化。
优化1
ConcurrentHashMap引入了更多的锁,为每个链表(桶)都分配了一把锁,降低了锁冲突的概率 。

如上,进行读写操作时,如果访问的是不同的桶,那么因为加锁对象不同,显然不会发生锁冲突,这与HashTable只要进行读写就加锁的方式相比,显然有了较大性能提升(降低了锁冲突的概率)。
引入多把锁,也就是需要更多的不同的锁对象,会造成更多的空间开销吗?
不会,空间上不需要额外的开销。对synchronized来说,任何对象都可以作为锁对象,因此可以将链表的头结点作为锁对象。
优化2
通过CAS来对维护size变量(无锁,因此更高效)
优化3
对扩容原理进行了优化。
传统的扩容过程:
某次put时,如果达到了上限,触发扩容,创建新的数组,将原来的键值对搬运到新数组中。该搬运过程是在一次put中完成的。
优化后的扩容过程:
触发扩容后,接下来的一部分时间,每次进行put/get等操作时,都会搬运一部分元素,确保不会某一次put/get特别慢,即将压力平摊了。
值得说明的是,ConcurrentHashMap在使用上和普通的HashMap基本一致。