【多线程】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基本一致。


相关推荐
Bdygsl2 小时前
数据结构 —— 队列
数据结构
野生技术架构师2 小时前
SpringBoot健康检查完整指南,避免线上事故
java·spring boot·后端
疯狂成瘾者2 小时前
Lombok 可以生成哪些类方法
java·tomcat·maven
七夜zippoe2 小时前
MyBatis插件开发-实现SQL执行耗时监控
java·sql·mybatis·springboot·责任链
水灵龙2 小时前
文件管理自动化:.bat 脚本使用指南
java·服务器·数据库
lbb 小魔仙2 小时前
【Java】Spring Cloud 微服务架构入门:五大核心组件与分布式系统搭建
java·spring cloud·架构
2501_944441752 小时前
Flutter&OpenHarmony商城App用户中心组件开发
java·javascript·flutter
黄昏恋慕黎明2 小时前
快速上手mybatis(一)
java·数据库·mybatis
编程之路,妙趣横生2 小时前
数据结构(十二) 位图 & 布隆过滤器
数据结构