多线程

CAS

2021-06-23  本文已影响0人  念䋛

锁按照粒度分为悲观锁和乐观锁
(1)悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。synchronized和ReentrantLock都是悲观锁.
(2)乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,CAS就是乐观锁最好的体现.

CAS(Compare And Swap) 比较并交换,在项目中的都是配合循环来实现乐观锁
在java中cas的使用需要Unsafe类,看名字知道是非安全类,不建议使用,所以在平时很少单独的使用cas,都是使用已经包装好的类,比如JUC中的atomic包下的类.
AtomicInteger atomicInteger = new AtomicInteger ();


image.png

atomicInteger.getAndIncrement ()方法源码

public final int getAndAddInt(Object var1, long var2, int var4) {
    var1是对象本身
var2 是修改值在内存中的地址
var5 要修改的值
var4 1
int var5;
    do {
//从主内存中获取最新的值到工作内存
        var5 = this.getIntVolatile(var1, var2);
//修改值,并同步到主内存,如果不相等返回false,继续while循环,如果相等则加一并同步到内存
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

Cas的缺点

  1. Cas和自旋配合使用实现了乐观锁,会出现多个线程同时执行的时候,while多次循环,依然不能保证用户内存和主内存数值一致,这样会增加cpu的使用率,不过是不会出现死循环的现象.
  2. Cas只是对一个变量操作实现了原子性,不会对一段代码实现原子性.
  3. Cas会出现ABA问题
    ABA问题


    image.png

    虽然中间间隔的时间非常短,也会出现ABA问题,那么什么是ABA问题
    比如线程1中var5从主内存中获取变量值为 10,代码向下执行到while判断,var5和主内存中的10是否相等,如果相等将10+1,同步主内存和用户内存,
    如果
    线程1:var5获取变量为10
    线程2:一个完整的cas,将变量减一为9
    线程3:一个完整的cas,将变量加一为10
    线程1:while判断,主内存变量依然为10,与用户内存相等,将10+1,同步到主内存和用户内存
    虽然结果和我们得到的一样,但是变量中间经过了两次变化.
    那么如何解决ABA问题呢,
    AtomicStampedReference

User user = new User ();
User user1 = new User ();
AtomicStampedReference<User> atomicStampedReference = new AtomicStampedReference (user,1);
/**
 * 第一个参数老值
 * 第二个参数新值
 * 第三个参数老版本号
 * 第四个参数新版本号,注意加一的动作
 */
boolean b = atomicStampedReference.compareAndSet (user, user1, atomicStampedReference.getStamp (),  atomicStampedReference.getStamp ()+1);
User reference = atomicStampedReference.getReference ();
System.out.println (reference == user1);
AtomicInteger atomicInteger = new AtomicInteger ();
atomicInteger.getAndIncrement ();

initialRef:是变量user
initialStamp:标签
Pair就是一个静态内部类

private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

compareAndSet

public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
//判断是否是一样的user
        expectedReference == current.reference &&
//判断版本号是否一致
        expectedStamp == current.stamp &&
//判断新的user和新的版本号
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
//如果主内存与当前内存的user和版本号相同,则将新的user和版本号更新主内存
casPair(current, Pair.of(newReference, newStamp)));
}

手写一个cas 这里只是一个示例,因为unsafe类在实际生产中不建议使用

public class CASTest {
    static Unsafe unsafe;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //创建unsafe
        Class<?> clazz = Unsafe.class;
        Field f = clazz.getDeclaredField ("theUnsafe");
        f.setAccessible (true);
        unsafe = ( Unsafe ) f.get (clazz);
        User user = new User ();
        user.setName ("zhangsan");
        long headOffset = 0;
        try {
            //偏移量,compareAndSwapObject方法的第二个参数
            headOffset = unsafe.objectFieldOffset (User.class.getDeclaredField ("name"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace ();
        }
        boolean b = unsafe.compareAndSwapObject (user, headOffset, "zhangsan", "lisi");
        System.out.println (user.getName ());
    }
}

结果为 lisi
上面的代码共两点

  1. unsafe是如何创建的,在实际生产中是不建议使用
  2. User的name为zhangsan
    当执行compareAndSwapObject的时候,传入的zhangsan和user对象的name的zhangsan是相等的,所以比较并交换,当前线程的值和主内存的值比较,传入值和user的name值相等,将lisi赋值给user的name属性,就是当前线程和主线程的值都为lisi,而其他线程并不是lisi,需要其他线程在比较并交换的时候先将本线程的值和主内存比较,得到最新的值lisi
    如果user的name属性初始为zhangsan1,当比较并交换的时候, compareAndSwapObject传入的值为zhangsan 而user的name属性为zhangsan1不相等,那么当前线程的user在寄存器中的副本的值不变依然为zhangsan1 不会将lisi赋值到user的name属性,并返回b为false.
上一篇 下一篇

猜你喜欢

热点阅读