【多线程】CAS和哈希表

文章目录


一、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基本一致。


相关推荐
瑞克-桑切斯15 分钟前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
海的透彻5 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat5 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
后台模板学习6 小时前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt
w_zero_one6 小时前
算法--构造
java·数据结构·算法
SL_staff6 小时前
制造业订单交付崩盘?用四层协同框架重建交付可信度(技术实践)
java·产品·全栈
SL_staff7 小时前
打破风控迭代死循环:用决策编排重构业务与技术协作链路
java·产品·全栈
Mikko77 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
.Bryce.7 小时前
从零到一开发技术名词大全:从需求分析到 AI Agent 工程
java·网络·人工智能·agent·需求分析·agent评测
SL_staff7 小时前
JVS-APS的7个刚性约束设计:从无限产能幻觉到可执行排产的工程实践
java·spring cloud·全栈