【JUC并发编程】CAS原子类底层原理&&源码分析&&手写AtomicInteger&&手写Lock锁

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 【JUC并发编程】CAS原子类底层原理&&源码分析&&手写AtomicInteger&&手写Lock锁,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、什么是CAS

CAS: Compare and Swap,翻译成比较并交换。 执行函数CAS(V,E,N)
CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。
保证线程安全性的问题 原子性

V: 全局共享变量 原子类中 value值 ===0
E: 旧的预期值
N: 修改的新值
CAS:无锁—-自旋控制乐观锁
缺点:自旋 会消耗的cpu的资源 cas 死循环 空转问题 cpu飙高
底层基于unsafe类实现
在这里插入图片描述
This === atomicInteger 对象;
Valueoffset == 值偏移量;

// paramObject=atomicInteger 对象
// Valueoffset ==值偏移量;
// paramInt=1
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
	// 循环目的 就是为了cas的重试
    while (true) {
	// paramObject=atomicInteger 对象
	// Valueoffset == 值偏移量;
	// 获取当前的atomicInteger  中 value值
        int i = getIntVolatile(paramObject, paramLong);==i==000
        if (compareAndSwapInt(paramObject, paramLong, i, i + paramInt))
            return i;
    }
}

二、同步之原子类(Atomic类)

Java并发包类库
原子类 cas

比如我们所学习的:原子类同步之原子类(Atomic类)

1. 原子更新基本类型类 cas

AtomicBoolean: 原子更新布尔类型。
AtomicInteger: 原子更新整型。
AtomicLong: 原子更新长整型。

2. 原子更新数组

AtomicIntegerArray: 原子更新整型数组里的元素。
AtomicLongArray: 原子更新长整型数组里的元素。
AtomicReferenceArray: 原子更新引用类型数组里的元素。

3. 原子更新引用类型

AtomicReference: 原子更新引用类型。
AtomicReferenceFieldUpdater: 原子更新引用类型的字段。
AtomicMarkableReferce: 原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型。

4. 原子更新字段类

AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。

5. JDK8新增原子类

DoubleAccumulator
LongAccumulator
DoubleAdder
LongAdder

Java 层面 Unsafe实现

三、使用atomicInteger计数

atomicInteger 计数能够保证线程安全问题。

public class Thread01 extends Thread {
    //    private static int sum = 0;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public void run() {
        sum();
    }

    public void sum() {
        for (int i = 0; i < 10000; i++) {
//            sum++;
            atomicInteger.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread01 t1 = new Thread01();
        Thread01 t2 = new Thread01();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("sum:" + atomicInteger.get());
    }
}

四、使用atomicInteger底层原理

atomicInteger底层基于CAS实现

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。
Unsafe类是”final”的,不允许继承。且构造函数是private的

传递三个参数 【this(atomicInteger this) valueOffset(成员变量在内存中偏移量) ,1 】+1

Value=对象内存地址+内存偏移量
CAS前置知识Unsafe
Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得 java9已经移除Unsafe

Cas底层 Unsafe类实现—–java层面
Cas 能够保证线程安全性 数据的原子性——cpu硬件支撑

private static AtomicInteger atomicInteger = new AtomicInteger();

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    // 1.使用反射获取theUnsafe属性
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    // 2.获取到unsafe
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    // 1.获取域的偏移地址
    long idOffset = unsafe.objectFieldOffset(Lock.class.getDeclaredField("state"));
    Lock lock = new Lock();
    boolean result = unsafe.compareAndSwapInt(lock, idOffset, 0, 1);
    System.out.println((ClassLayout.parseInstance(lock).toPrintable()));
    System.out.println(result);
}

static class Lock {
    int state;
}
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
    while (true) {
        int i = getIntVolatile(paramObject, paramLong);
        if (compareAndSwapInt(paramObject, paramLong, i, i + paramInt))
            return i;
    }
}

从对象的内存处开始,获得原始字节偏移量,用于存储实力对象的内存
在这里偏移量的意思就像我们 new 一个数组,数组的地址就是数组地一个元素的地址,假如数组地址是 a,第二个元素就是a+1,其中+1就是偏移量。对应的对象的一个属性的偏移量就是其对象的地址开始增加,增加的数就是这个filed的偏移量。
对于VALUE这个值我们知道了,他是AtomicInteger中的value属性对应的偏移量,就是对象地址+VALUE = value的地址

循环:获取当前atomicInteger 原子类的 value=0 i=0
compareAndSwapInt(1,1,1+1);
在这里插入图片描述
AtomicInteger 底层如何实现
1.创建了一个AtomicInteger 类
2.先定义一个 int类型value值
3.底层都是基于unsafe类实现—-

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// 参数1 this new AtomicInteger 
// 参数2  valueOffset
// 参数3  1

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do { 
// var5 
        var5 = this.getIntVolatile(var1, var2);
//this.compareAndSwapInt(var1, var2, var5, var5 + var4)
// var1 this new AtomicInteger 对象
// var2 value的值 offset
// var5---当前value值=0 (旧预期值 E)
// var5(0) + var4(1)---当前value值+1
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

五、compareAndSet原理分析

compareAndSet(e,n);
E==V 则将N的值赋值给V;

1. 手写AtomicInteger

public class DemoAtomicInteger {
    // 设定v=0
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public void incrementAndGet() {
//        Integer v = atomicInteger.get();
        Integer e = atomicInteger.get();
        // 一直重试不断循环 如果cas 失败的 则一直不断重试
        for (; ; ) {
            if (atomicInteger.compareAndSet(e, e + 1)) {
                return;
            }
        }
    }

    public static void main(String[] args) {
        DemoAtomicInteger demoAtomicInteger = new DemoAtomicInteger();
        demoAtomicInteger.incrementAndGet();
        demoAtomicInteger.incrementAndGet();
        demoAtomicInteger.incrementAndGet();
        System.out.println(atomicInteger.get());
    }
}

2. 手写Lock锁

Aqs+cas实现
默认锁的状态是为=0 如果获取到锁 则锁的状态就是为1

public class DemoLock {
    /**
     * 默认锁的状态是为=0  如果获取到锁  则锁的状态就是为1
     */
    private AtomicInteger lockState = new AtomicInteger(0);
    private Thread cuLockThread;

    /**
     * 获取锁
     */
    public void lock() {
        for (; ; ) {
            if (lockState.compareAndSet(0, 1)) {
                return;
            }
        }
    }

    /**
     * 释放锁
     */
    public void unLock() {
        for (; ; ) {
            if (lockState.compareAndSet(1, 0)) {
                cuLockThread = null;
                return;
            }

        }

    }

    public static void main(String[] args) {
        DemoLock demoLock = new DemoLock();
        System.out.println("3");
    }
}

3. CAS aba的问题

Cas主要检查 内存值V与旧的预值值=E是否一致,如果一致的情况下,则修改。
这时候会存在ABA的问题:
如果将原来的值A,改为了B,B有改为了A 发现没有发生变化,实际上已经发生了变化,所以存在Aba问题。
解决办法:通过版本号码,对每个变量更新的版本号码做+1

解决aba问题是否大:概念产生冲突,但是不影响结果,换一种方式 通过版本号码方式。
在这里插入图片描述

4. AtomicMarkableReference

(1)第一个参数expectedReference:表示预期值。
(2)第二个参数newReference:表示要更新的值。
(3)第三个参数expectedStamp:表示预期的时间戳。
(4)第四个参数newStamp:表示要更新的时间戳。

public class Test004 {
    // 注意:如果引用类型是Long、Integer、Short、Byte、Character一定一定要注意值的缓存区间!
    // 比如Long、Integer、Short、Byte缓存区间是在-128~127,会直接存在常量池中,而不在这个区间内对象的值则会每次都new一个对象,那么即使两个对象的值相同,CAS方法都会返回false
    // 先声明初始值,修改后的值和临时的值是为了保证使用CAS方法不会因为对象不一样而返回false
    private static final Integer INIT_NUM = 1000;
    private static final Integer UPDATE_NUM = 100;
    private static final Integer TEM_NUM = 200;
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(INIT_NUM, 1);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                Integer value = (Integer) atomicStampedReference.getReference();
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + " : 当前值为:" + value + " 版本号为:" + stamp);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // value 旧值 内存中的值   UPDATE_NUM 修改的值
                if (atomicStampedReference.compareAndSet(value, UPDATE_NUM, 1, stamp + 1)) {
                    System.out.println(Thread.currentThread().getName() + " : 当前值为:" + atomicStampedReference.getReference() + " 版本号为:" + atomicStampedReference.getStamp());
                } else {
                    System.out.println("版本号不同,更新失败!");
                }
            }
        }, "线程A").start();

    }
}

六、CAS 优点

CAS 优点: 避免用户态到内核态切换
缺点: 非常消耗cpu的资源

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131219.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!