Linux-读写锁原理-CAS无锁编程原理和缺陷

1. 读写锁

1.1 适用场景

  大量读,少量写

1.2 原理

  1. 多个程序可以并行的对临界资源进行读操作,程序不会产生二义性结果。
  2. 读写锁的内部有一个引用计数,来统计当前以读模式打开的读写锁的线程的数量。
  3. 通过引用计数来判断当前读写锁是否还有线程以读模式打开,判断什么时候读写锁是空闲的,没有被占用。

1.3 读写锁的三种模式

  1. 以读模式打开读写锁
  2. 以写模式打开读写锁
  3. 不加锁

1.4 面试题

问题: 线程A读模式,线程B读模式,并且已经获取读写锁,线程C想要以写模式打开,线程D想要以读模式打开,问线程D能不能直接获得读写锁?

不能,因为如果D能获取读写锁,那么我们假设后面全是想要获取读模式的线程,那么线程C将永远无法获取读写锁,因此,在将存在一个想要以写模式打开的线程的时候,后面想要获取读模式的线程将会进入等待队列,直到线程C解锁。

1.5 接口

1.初始化接口
  int pthread_rwlock_init
Linux-读写锁原理-CAS无锁编程原理和缺陷
2.销毁接口
  int pthread_rwlock_destroy
Linux-读写锁原理-CAS无锁编程原理和缺陷
3.读方式
  int pthread_rwlock_rdlock
  int pthread_rwlock_tryrdlock
Linux-读写锁原理-CAS无锁编程原理和缺陷
4.写模式
  int pthread_rwlock_wrlock
  int pthread_rwlock_trywrlock
Linux-读写锁原理-CAS无锁编程原理和缺陷
5.解锁接口
  int pthread_rwlock_unlock
Linux-读写锁原理-CAS无锁编程原理和缺陷

2. CAS无锁编程

2.1 前言

  CAS(Compare And Swap,比较并交换),CAS根据其设计思想,可以划分为乐观锁。不同于Linux-读写锁原理-CAS无锁编程原理和缺陷
  现在无论运行多少遍,结果都是2000,也就是在没有加锁的情况下,写出了线程安全的count++操作,因此实现的关键就是AtomicInteger#getAndIncrement()方法。所以我们直接看下getAndIncrement()的源码。

/**  * Atomically increments by one the current value.  * 以原子方式将当前值增加一  */ public final int getAndIncrement() {     return unsafe.getAndAddInt(this, valueOffset, 1); }  public final int getAndAddInt(Object var1, long var2, int var4) {     int var5;     do {         var5 = this.getIntVolatile(var1, var2);     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));      return var5; } 

  可以该实现主要调用了this.compareAndSwapInt(…)方法,该方法就是便是CAS(Compare And Swap,比较并交换)。既然是比较和交换,那我们应该明确两点:比较什么、交换什么?
  CAS操作涉及到三个变量(V、E、N),V表示要更新的变量(工作内存中该变量的值)、E表示期望值(主内存中该变量的值)、N表示新值。
  首先判断变量当前值(V)是否等于期望值(E),不等于则说明在当前线程修改这个变量,同步回主内存之前,有别的线程已经修改过这个变量并且同步回了主内存。所以当前线程不能把值同步回主内存,而是重新从主内存中读取该值,重复这整个操作,直到当前值(V)等于期望值(E),才将新值同步回主内存。

2.3 过程描述

  1. 线程A从主内存中读入变量count作为值V;
  2. 线程A读取count的最新值,作为期望值E
  3. 线程A把值(V)和期望(E)比较是否相等,相等就把新值(N)写回主内存,不相等就回到操作1

  其中第三步是原子操作,比较V和E局势为了保证变量count没有被其他线程修改过。

2.4 CAS缺陷

  • 自旋的实现方式让所有线程都处于高频运行,争抢CPU的状态,如果操作长时间不成功,会带来很大的CPU消耗
  • 仅针对单个变量的操作,不能用于多个变量来实现原子操作
  • 会存在ABA问题

  ABA问题指主存当中的count变量从100变成101,再变成100的这种情况,这时候虽然还是100,但是这时候的100和原来100的含义不同,但是对于线程A是无法感知的,如果在实际开发当中不需要考虑ABA的影响,就可以使用CAS。

版权声明:玥玥 发表于 2021-05-22 4:03:33。
转载请注明:Linux-读写锁原理-CAS无锁编程原理和缺陷 | 女黑客导航