CAS(乐观锁)与AtomicInteger原理

1.什么是锁?

什么是锁,编程中我们提到锁通常是指并发资源锁。那么啥是资源锁,我们通俗地理解加锁行为,当一个资源被两个程序(进程)争夺时,为了保证资源只能够被其中一方使用,因此在获取资源后给其加锁,使用期间不让其他进程获得的过程。

2.悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

3.乐观锁(CAS和版本号机制)

3.1.CAS

CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。

实现思想 CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 CAS 指令之前返回该位置的值。而我们可以使用自旋锁,循环CAS,重新读取该变量再尝试再次修改该变量,也可以放弃操作。

CAS(乐观锁)与AtomicInteger原理_第1张图片

CAS操作由处理器提供支持,是一种原语。原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。如 Intel 处理器,比较并交换通过指令的 cmpxchg 系列实现。

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

3.1.1Java实现CAS操作

Unsafe类,在sun.misc包下,不属于Java标准。Unsafe类提供一系列增加Java语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等。其中与CAS相关的方法有以下几个:

//var1为CAS操作的对象,offset为var1某个属性的地址偏移值,expected为期望值,var2为要设置的值,利用JNI来完成CPU指令的操作
public final native boolean compareAndSwapObject(Object var1, long offset, Object expected, Object var2);
public final native boolean compareAndSwapInt(Object var1, long offset, int expected, int var2);
public final native boolean compareAndSwapLong(Object var1, long offset, long expected, long var2);

 

/** 如果CAS成功,return oldValue, oldValue =  oldValue + addValue
     *  如果CAS失败,自旋,一直运行,直到成功为止
     */
    public final Xxx getAndAddXxx(Object var1, long offset, long addValue) {
        int oldValue;
        do {
            oldValue = this.getIntVolatile(var1, offset);
        } while(!this.compareAndSwapInt(var1, offset, oldValue, oldValue + addValue));

        return oldValue;
    }

    /** 如果CAS成功,return oldValue, oldValue =  newValue
     *  如果CAS失败,自旋,一直运行,直到成功为止
     */
    public final Xxx getAndSetXxx(Object var1, long offset, Object newValue) {
        int oldValue;
        do {
            oldValue = this.getXxxVolatile(var1, offset);
        } while(!this.compareAndSwapXxx(var1, offset, oldValue, newValue));

        return oldValue;
    }

一般不建议使用Unsafe类,除非对它有很深入的了解。

java.util.concurrent包中大量使用了CAS原理,如AtomicInteger类,都是调用上面几个Unsafe方法保证多线程数据的正确性

以下是AtomicInteger的CAS操作相关源码

public class AtomicInteger extends Number implements java.io.Serializable {
       private static final long serialVersionUID = 6214790243416807050L;
 
     // setup to use Unsafe.compareAndSwapInt for updates
     // Unsafe类,提供一系列增强Java的功能,如内存管理、操作类/对象/变量、多线程同步等。不建议开发者调用
     private static final Unsafe unsafe = Unsafe.getUnsafe();
     // 获取对象某个属性的地址偏移值
     private static final long valueOffset;
 
     static {
         try {
             // value相对“起始地址”的偏移量
             valueOffset = unsafe.objectFieldOffset
                     (AtomicInteger.class.getDeclaredField("value"));
         } catch (Exception ex) { throw new Error(ex); }
     }
 
     // value值, volatile修饰,保证不同线程间的可见性
     private volatile int value;
     public AtomicInteger(int initialValue) { value = initialValue; }
     public AtomicInteger() {}
 
     public final int get() { return value; }
     public final void set(int newValue) { value = newValue; }
 
     /**
      * Eventually sets to the given value.
      *
      * @param newValue the new value
      * @since 1.6
      */
     public final void lazySet(int newValue) {
         //有序或者有延迟的putIntVolatile方法
         unsafe.putOrderedInt(this, valueOffset, newValue);
     }
 
     /**
      * Atomically sets to the given value and returns the old value.
      * @param newValue the new value
      * @return the previous value
      */
     public final int getAndSet(int newValue) {
         return unsafe.getAndSetInt(this, valueOffset, newValue);
     }
 
     /**
      * Atomically sets the value to the given updated value
      * if the current value {@code ==} the expected value.
      * @param expect the expected value
      * @param update the new value
      * @return {@code true} if successful. False return indicates that
      * the actual value was not equal to the expected value.
      */
     public final boolean compareAndSet(int expect, int update) {
         // JNI调用,实现CAS
         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
     }
 
     /**
      * i++ 操作
      * Atomically increments by one the current value.
      * @return the previous value
      */
     public final int getAndIncrement() {
         return unsafe.getAndAddInt(this, valueOffset, 1);
     }
 
     /**
      * i-- 操作
      * Atomically decrements by one the current value.
      * @return the previous value
      */
     public final int getAndDecrement() {
         return unsafe.getAndAddInt(this, valueOffset, -1);
     }
 
     /**
      * return i, i = i + n 操作
      * Atomically adds the given value to the current value.
      * @param delta the value to add
      * @return the previous value
      */
     public final int getAndAdd(int delta) {
         return unsafe.getAndAddInt(this, valueOffset, delta);
     }
 
     /**
      * ++i 操作
      * Atomically increments by one the current value.
      * @return the updated value
      */
     public final int incrementAndGet() {
         return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
     }
 
     /**
      * --i 操作
      * Atomically decrements by one the current value.
      * @return the updated value
      */
     public final int decrementAndGet() {
         return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
     }
 
     /**
      * i = i + n ,return i操作
      * Atomically adds the given value to the current value.
      * @param delta the value to add
      * @return the updated value
      */
     public final int addAndGet(int delta) {
         return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
     }
     // 其余函数,略...

3.1.2实现无阻塞多线程争抢资源的模型实例

import java.util.concurrent.atomic.AtomicBoolean;

public class AtomicBooleanTest implements Runnable {

	private static AtomicBoolean flag  = new AtomicBoolean(true);
	
	public static void main(String[] args) {
		AtomicBooleanTest abt = new AtomicBooleanTest();
		Thread thread0 = new Thread(abt);
		Thread thread1 = new Thread(abt);
//		Thread thread2 = new Thread(abt);
		thread0.start();
		thread1.start();
//		thread2.start();
	}
	
	@Override
	public void run() {
		System.out.println("thread:"+Thread.currentThread().getName()+";flag:"+flag.get());
		if(flag.compareAndSet(true, false)) {
			System.out.println(Thread.currentThread().getName()+";flag:"+flag.get());
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			flag.set(true);
		}else {
			System.out.println("重试机制thread:"+Thread.currentThread().getName()+";flag:"+flag.get());
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			run();
		}

	}

}

输出结果:

thread:Thread-1;flag:true
thread:Thread-0;flag:true
Thread-1;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:false
重试机制thread:Thread-0;flag:false
thread:Thread-0;flag:true
Thread-0;flag:false

这里无论怎么运行,Thread-1、Thread-0都会执行if=true条件,而且还不会产生线程脏读脏写,这是如何做到的了,这就用到了我们的compareAndSet(boolean expect,boolean update)方法
我们看到当Thread-1在进行操作的时候,Thread-0一直在进行重试机制,程序原理图:

CAS(乐观锁)与AtomicInteger原理_第2张图片

这个图中重最要的是compareAndSet(true,false)方法要拆开成compare(true)方法和Set(false)方法理解,是compare(true)是等于true后,就马上设置共享内存为false,这个时候,其它线程无论怎么走都无法走到只有得到共享内存为true时的程序隔离方法区。

看到这里,这种CAS机制就是完美的吗?这个程序其实存在一个问题,不知道大家注意到没有?

但是这种得不到状态为true时使用递归算法是很耗cpu资源的,所以一般情况下,都会有线程sleep。

3.1.3.CAS缺点

CAS有几个缺点:

1)ABA问题。当第一个线程执行CAS操作,尚未修改为新值之前,内存中的值已经被其他线程连续修改了两次,使得变量值经历 A -> B -> A的过程。

  解决方案:添加版本号作为标识,每次修改变量值时,对应增加版本号; 做CAS操作前需要校验版本号。JDK1.5之后,新增AtomicStampedReference类来处理这种情况。

2)循环时间长开销大。如果有很多个线程并发,CAS自旋可能会长时间不成功,会增大CPU的执行开销。

3)只能对一个变量进原子操作。JDK1.5之后,新增AtomicReference类来处理这种情况,可以将多个变量放到一个对象中。

3.2.版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。

  当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

如果文章对您有所帮助,可以点一下推荐哦

4.java.util.concurrent包介绍

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  1. A线程写volatile变量,随后B线程读这个volatile变量。
  2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

  1. 首先,声明共享变量为volatile;
  2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
  3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

CAS(乐观锁)与AtomicInteger原理_第3张图片

参考:

https://www.cnblogs.com/Shuuichi/p/10590710.html

https://www.jianshu.com/p/ae25eb3cfb5d

https://blog.csdn.net/u011506543/article/details/82392338

你可能感兴趣的:(Android拓展)