关于java:话说cas

43次阅读

共计 5715 个字符,预计需要花费 15 分钟才能阅读完成。

一、前言

  1. cas 个别认为是 compare and swap 也能够认为是 compare and set
  2. cas 波及三个值

(1)P 变量内存地址

​(2)E 期望值 ,CPU 做计算之前拿进去的旧值

​ (3) X 须要设置的新值

原子操作为:拿出内存地址以后的值 A , 比拟 A == E ? 是:设置 P 内存的值为 X 否:完结。。失败

  1. (1) 第一篇 话说 synchronized 画过 CAS 的流程图 咱们再来一张?

(2) CAS 面试常常问的一个是 ABA 问题 什么是 ABA ? 上图

(3) 有人说 ABA 不影响啊 我反正冀望的值是 A 你最初是 A 就得了呗

​ 这个还要看具体的业务,拿生存中例子来说,银行职员小孙,偷拿了银行 100 万,

而后去投资赚了 20 万,最初把 100 万还回去。你细品。。银行能容许吗

(4)ABA 的解决方案 版本 version 怎么解决?

二、DEMO

1. CAS 简略应用

如果有一个值 int count,2 个线程 每个线程给 count 加 5000 次 1
按道理说 每个人给你 5000 你应该有 1 万块

public class CasTest {
    public  static int count = 0;
    public static void main(String[] args) throws InterruptedException {new Sub("第一个").start();
        new Sub("第二个").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest.count);
    }
}

class Sub extends  Thread{
    private String name;
    public Sub(String name) {this.name = name;}
    public void run() {System.out.println(name+"开始 +");
        for (int i = 0; i < 5000; i++) {
            try {CasTest.count = CasTest.count+1;} catch (Exception e) {e.printStackTrace();
            }
        }
        System.out.println(name+"加完了..");
    }
}

执行后果:第一个开始 +
第二个开始 +
第一个加完了..
第二个加完了..
count=6811

解决方案 1 加锁 synchronized 或者 lock 都能够

public class CasTest02 {
    public  static Integer count = 0;
    public static void main(String[] args) throws InterruptedException {new Sub02("第一个").start();
        new Sub02("第二个").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest02.count);
    }
}

class Sub02 extends  Thread{
    private String name;
    public Sub02(String name) {this.name = name;}
    public void run() {System.out.println(name+"开始 +");
        for (int i = 0; i < 500; i++) {
            try {
                // 加锁
                synchronized (CasTest02.class) {CasTest02.count = CasTest02.count+1;}
            } catch (Exception e) {e.printStackTrace();
            }
        }
        System.out.println(name+"加完了..");
    }
}

解决方案 2: CAS java 自带的原子类 AtomicInteger

​ 读者读到这里能够理解一下 LongAdder

public class CasTest03 {public  static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {new Sub03("第一个").start();
        new Sub03("第二个").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest03.count);
    }
}

class Sub03 extends  Thread{
    private String name;
    public Sub03(String name) {this.name = name;}
    public void run() {System.out.println(name+"开始 +");
        for (int i = 0; i < 5000; i++) {
            // 加锁
            CasTest03.count.incrementAndGet();}
        System.out.println(name+"加完了..");
    }
}
执行后果:第一个开始 +
第二个开始 +
第二个加完了..
第一个加完了..
count=10000

2. ABA 问题

这里简略复现一个 ABA 问题 可能不是很准确 读者敌人领会意思即可

public class CasTest04 {public  static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {new Thread(()->{count.compareAndSet(0,1);
            count.compareAndSet(1,0);
            System.out.println("线程 1 把 count 从 0 批改为 1  再从 1  批改为 0");
        },"线程 1").start();

        new Thread(()->{
           try {TimeUnit.SECONDS.sleep(1);
               // 这里是 0  然而曾经不是他所心愿的那个 0 了
               count.compareAndSet(0,4);
               System.out.println("线程 2 把 count 从 0 批改为 4");
           } catch (Exception e) {e.printStackTrace();
           }
        },"线程 2").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+count);
    }
}
执行后果:线程 1 把 count 从 0 批改为 1  再从 1  批改为 0  
线程 2 把 count 从 0 批改为 4
count=4

ABA 解决 加版本

public class CasTest05 {public  static AtomicStampedReference<Integer> count = new AtomicStampedReference<>(0,0);
    public static void main(String[] args) throws InterruptedException {new Thread(()->{
            try {
                // 等 1 秒  让线程 2 拿到版本
                TimeUnit.SECONDS.sleep(1);
                boolean res = count.compareAndSet(
                    0, 
                    1, 
                    count.getStamp(), 
                    count.getStamp() + 1);
                boolean res2 = count.compareAndSet
                    (1, 
                     0, 
                     count.getStamp(), 
                     count.getStamp() + 1);
                System.out.println("线程 1 把 count 从 0 批改为 1  再从 1  批改为 0"
                                   + (res2 ? "胜利!":"失败!"));
            }catch (Exception r){r.printStackTrace();
            }

        },"线程 1").start();

        new Thread(()->{
           try {
               // 版本 
               int stamp = count.getStamp();
               TimeUnit.SECONDS.sleep(2);
               // 这里是 0  然而曾经不是他所心愿的那个 0 了  版本曾经变了 
               boolean res = count.compareAndSet(0,4,stamp,stamp+1);
               System.out.println("线程 2 把 count 从 0 批改为 4" 
                                  + (res ? "胜利!":"失败!"));
           } catch (Exception e) {e.printStackTrace();
           }
        },"线程 2").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+count.getReference());
    }
}

三、伪装学术讨论

/**
 * @author 木子的昼夜
 */
public class CasTest {public static void main(String[] args) {
        // JUC 包里的原子类  线程平安的 
        AtomicInteger ai = new AtomicInteger();
        // 加 1 并返回 
        Integer res = ai.incrementAndGet();
        System.out.println(res);
        
        // 上边这句话的意思相当于 
        int i = 0;
        i = i+1;
        int resi = i;
        System.out.println(resi);
    }
}

 /**
     * Atomically increments by one the current value.
     * 以后值主动加 1 
     * @return the updated value 
     * 返回更新了的值
     */
    public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // Unsafe.java#getAndAddInt
    public final int getAndAddInt(Object obj, long offset, int step) {
        int E;
        do {var5 = this.getIntVolatile(obj, offset);
            // 这里不肯定能一次就胜利哦  会执行屡次 
            // 跟一个姑娘表白 一次不成 你就兴冲冲走了?活该独身..
        } while(!this.compareAndSwapInt(obj, offset, E, E + step));

        return var5;
    }
    // Unsafe.java#getIntVolatile 这个办法是获 对象 obj 内存开始地址 绝对偏移地位 offset 对应的值
    // 说白了 就是获取对象对应字段的值 
    public native int getIntVolatile(Object obj, long offset);

    // 
    public final native boolean compareAndSwapInt(
        Object obj,  // 被批改属性的对象 
        long offset, // 被批改字段绝对于以后对象内存首地址偏移量 能够通过他间接去内存拿数据
        int E, // 期望值 
        int X);// 须要设置的新值 
// obj 设置属性的对象 offset 字段绝对于类内存起始地位偏移量  e 期望值  x 要设置的值 
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
  // 转换对象格局 jobject- > oop 
  oop p = JNIHandles::resolve(obj);
  if (p == NULL) {
     // 获取 filed 对应的内存地址 对象起始地址 + 偏移量
    volatile jint* addr = (volatile jint*)index_oop_from_field_offset_long(p, offset);
    // 把 addr 内存对应的值 设置为 x 前提是内存值要等于 e 
    return RawAccess<>::atomic_cmpxchg(addr, e, x) == e;
  } else {
    // 
    assert_field_offset_sane(p, offset);
    return HeapAccess<>::atomic_cmpxchg_at(p, (ptrdiff_t)offset, e, x) == e;
  }
} UNSAFE_END
// 这里就调用了 atomic_cmpxchg:零碎办法:原子比拟并替换计数值。atomic_cmpxchg(void* addr, T compare_value, T new_value) {if (is_hardwired_primitive<decorators>()) {
        const DecoratorSet expanded_decorators = decorators | AS_RAW;
        return PreRuntimeDispatch::atomic_cmpxchg<expanded_decorators>
            (addr, compare_value, new_value);
    } else {
        return RuntimeDispatch<decorators, T, BARRIER_ATOMIC_CMPXCHG>::atomic_cmpxchg
            (addr, compare_value, new_value);
      }
}

再底层就是零碎级别的实现了,CPU 实现 Atomic,我也是看文章看得,说是有 2 中形式

  1. 应用总线锁 总线就是老大 CPU 小 c 给总线发一个 LOCK 信号 总线收到之后 小 c 就独占共享内存了,其余 CPU
    就没有应用权限了,数据夸缓存行时应用总线锁 这时候不能用缓存锁
  2. 应用缓存锁 大多数时候 咱们只须要保障对某一块内存的操作时原子性即可,缓存锁就是如果内存区域被缓存再处理器的缓存行中,并且操作的时候缓存行被锁定了,那么当处理器计算完回写到内存时,处理器就把缓存行的地址批改了,如果这时有两一个处理器回写数据到缓存行,咦?生效了。。

下边连贯是我筹备写文章的目录,如果有读者感觉须要增加,请微信公众间接发消息给我。

https://www.processon.com/vie…

最初附上本人公众号刚开始写 愿一起提高:

留神 :以上文字 仅代表个人观点,仅供参考,如有问题还请指出,立刻马上连滚带爬的从被窝里进去改过。

正文完
 0