java并发 day04CAS 、原子整数、 原子引用、 原子数组、 字段更新器和原子累加器 、unsafe、 CPU缓存结构、 不可变类、 final的原理

CAS

compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。
CAS必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果。
volatile修饰的共享变量,会保证每次读操作都会从主内存中获取最新值。在CAS操作中,最新值与CAS代码中的获取值不一致的时候,会重新获取最新值并再次比较。当一致的时候,就会进行数值的修改,以此来保证数据的安全性。

CAS 的特点

结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

CAS 体现的是无锁并发、无阻塞并发

1、没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
2、但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

原子整数(整数)

AtomicBoolean
AtomicInteger
AtomicLong

getAndIncrement() 类似于 i++
incrementAndGet() 类似于 ++i
decrementAndGet() 类似于–i
getAndDecrement() 类似于 i–
getAndAdd(5) 先获取i再加5
addAndGet(-5) 先加5再获取i
getAndUpdate(p -> p * 2) 进行乘除操作
.updateAndGet(p -> p * 2) 进行乘除操作

原子引用(小数、字符串。。)

AtomicReference
AtomicMarkableReference
AtomicStampedReference
AtomicReference<BigDecimal> bigdecimal = new AtomicReference<>(new BigDecimal("10.1"));
AtomicReference<String> str = new AtomicReference<>("A");

ABA问题

@Slf4j
public class Test36 {

    static AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // 获取值 A
        String prev = ref.get();
        // 如果中间有其它线程干扰,发生了 ABA 现象
        other();
        TimeUnit.SECONDS.sleep(1);
        // 尝试改为 C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
    }

    private static void other() {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
        }, "t1").start();
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
        }, "t2").start();
    }
}

在这里插入图片描述

主线程仅能判断出共享变量的值与最初值 A 是否相同,不能察觉到这种从 A 改为 B 又 改回 A 的情况。

只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号。

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。

@Slf4j
public class Test37 {

    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // 获取值 A
        String prev = ref.getReference();
        // 获取版本号
        int stamp = ref.getStamp();
        log.debug("版本 {}", stamp);
        // 如果中间有其它线程干扰,发生了 ABA 现象
        other();
        TimeUnit.SECONDS.sleep(1);
        // 尝试改为 C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
    }

    private static void other() {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本为 {}", ref.getStamp());
        }, "t1").start();
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本为 {}", ref.getStamp());
        }, "t2").start();
    }
}

但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference,
boolean success = ref.compareAndSet(prev, next, true, false);

原子数组

AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
保护数组的元素

字段更新器和原子累加器

字段更新器

AtomicReferenceFieldUpdater // 域 字段
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常

在这里插入图片描述

public class Test40 {

    public static void main(String[] args) {
        Student stu = new Student();

        AtomicReferenceFieldUpdater updater =
                AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");

        System.out.println(updater.compareAndSet(stu, null, "张三"));
        System.out.println(stu);
    }
}

class Student {
    volatile String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

原子累加器

LongAdder
LongAdder性能比AtomicLong好
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。

unsafe

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

public class UnsafeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        System.out.println(unsafe);
    }
}

unsafe的CAS操作

public class UnsafeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        System.out.println(unsafe);

        //1、获取域得偏移地址
        long idOffSet = unsafe.objectFieldOffset(T.class.getDeclaredField("id"));
        long nameOffSet = unsafe.objectFieldOffset(T.class.getDeclaredField("name"));
        //2、执行CAS操作
        T t = new T();
        unsafe.compareAndSwapInt(t,idOffSet,0,1);
        unsafe.compareAndSwapObject(t,nameOffSet,null,"张三");

        log.info("t:{}",t);
    }
}
class T{
    volatile int id ;
    volatile String name;

    @Override
    public String toString() {
        return "T{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

unsafe原子整数类

public class MyAtomicInteger {
    public static void main(String[] args) throws InterruptedException {
        MyAtomicInteger atomicInteger = new MyAtomicInteger(1000);
        new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                atomicInteger.increment(1);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                atomicInteger.increment(1);
            }
        }).start();
        Thread.sleep(3000);
        int value = atomicInteger.getValue();
        log.info("value:{}",value);
    }
    private volatile int value;
    private static Unsafe unsafe;
    private static long valueOffset;

    static {
        Field theUnsafe = null;
        try {
            theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
            // value 属性在 MyAtomicInteger 对象中的偏移量,用于 Unsafe 直接访问该属性
            valueOffset = unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    private int getValue() {
        return value;
    }

    private void increment(int amount) {
        while (true) {
            int pre = this.value;
            int next = pre + amount;
            if(unsafe.compareAndSwapInt(this,valueOffset,pre,next)){
                break;
            };
        }
    }

}

CPU缓存结构

在这里插入图片描述

在这里插入图片描述

1、因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
2、而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64
byte(8 个 long)
3、缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
4、CPU要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
5、@sun.misc.Contended 的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的padding,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效

不可变类

不可变类在多线程中也可以保证数据得安全性。比如Sting类、DateTimeFormatter类
SimpleDateFormat并不是线程安全的。在 Java 8 后,提供了一个新的日期格式化类DateTimeFormatter以保证线程安全。

		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String format = formatter.format(LocalDate.now());
        System.out.println(format);
        TemporalAccessor parse = formatter.parse(format);
        System.out.println(parse);

不可变类的设计

使用final修饰类

  • 属性用 final 修饰保证了该属性是只读的,不能修改
  • 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性

String类就是使用保护性拷贝来保证数据的不可变。
通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】(全新的对象)

final的原理

1、设置final的原理
在这里插入图片描述
在这里插入图片描述

final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0的情况。
final通过加入写屏障,来保证数据对其他线程的可见性,以及防止写屏障之前的指令放到写屏障之后。

2、获取final变量的原理
在这里插入图片描述

在这里插入图片描述

final修饰的成员变量在字节码指令中会直接压入栈中,并不会通过堆(并不会走getStatic指令)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

halulu.me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值