CAS+Unsafe+Atomic

并发发展

1.synchronized

  • 阻塞锁机制(BIO)
  • 问题+缺点
1.拿到锁的线程一直不释放锁如何处理
2.大量资源竞争,消耗CPU,同时会造成死锁或其他安全问题
3.每次执行synchronized需上下文切换,消耗特大
4.大多数场景,并发操作粒度很小,synchronized有sjynd嫌疑

2.volatile

  • 解决synchronized问题
  • 非阻塞volatile解决共享变量在多线程间的可见性
  • 但volatile不保证原子性

3.CAS

  • JDK提供的非阻塞原子性操作,通过硬件保证比较-更新的操作原子性

CAS

1.概述

  • Compare And Swap
  • 线程安全的操作类
  • 无锁算法
  • 乐观锁的实现
悲观锁(独占锁synchronized):假设最坏的情况,只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起直到持有锁的线程释放锁
乐观锁:每次不加锁,假设没有冲突而去完成某项操作,如果发生冲突就重试,直到成功为止

2.CAS原理

  • 3个操作数:内存所存值V,原值A,要改的值B
  • 当且仅当V=A时,将V改为B并返回true,否则返回false
  • 若V和A一直不相等,则一直循环该CAS操作(自旋do-while)
    在这里插入图片描述

3.CAS问题

  • ABA问题
    在这里插入图片描述
  • CPU开销大(自旋)
  • 一次能保证一个共享变量的原子操作

Unsafe

1.概述

  • 提供类似C++的手动内存管理能力
  • 全限定名是sun.misc.Unsafe
  • final修饰类,不允许继承
  • private修饰构造方法,不允许实例化

2.作用

在这里插入图片描述

3.创建

  • 错误创建
private static final Unsafe unsafe = Unsafe.getUnsafe();
当使用getUnsafe获取时必须保证是根加载器BootStrap,否则抛出异常(java.lang.SecurityException: Unsafe)
	-->此处是Launcher$Application加载器
---------------------------------------
//getUnsafe底层
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    //isSystemDomainLoader** -->不为空(不是根类加载器)进入if,抛出异常
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}
---------------------------------------
//isSystemDomainLoader底层  -->说明类加载器为空时返回true,由双亲委派可知根加载器由C++编写为null
public static boolean isSystemDomainLoader(ClassLoader var0) {
	return var0 == null;
}
  • 正确创建(反射不讲武德)
static long offset;
private volatile int state = 0;
//创建Unsafe对象正确方式
static Unsafe unsafe;

static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        //获取此变量的值-->底层:theUnsafe = new Unsafe();(静态块中)
        unsafe = (Unsafe) field.get(null);
        //获取属性偏移量
        offset = unsafe.objectFieldOffset(unsafe_error.class.getDeclaredField("state"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4.方法

Atomic

1.原子基础数据类(AtomicInteger)

  • 方法
AtomicInteger atomicInteger = new AtomicInteger(10);
//安全自增,获取然后自增  10
System.out.println(atomicInteger.getAndIncrement());
//自增然后获取 12
System.out.println(atomicInteger.incrementAndGet());
//12
System.out.println(atomicInteger.get());
//添加后获取  14
System.out.println(atomicInteger.addAndGet(2));
//获取后设置  14
System.out.println(atomicInteger.getAndSet(2));
//2
System.out.println(atomicInteger.get());
  • incrementAndGet源码(自旋)
//incrementAndGet源码
public final long incrementAndGet() {
	//unsafe是如何创建??通过Unsafe.getUnsafe();
	//此处使用的是根加载器加载,因为Unsafe与根加载器都是sun公司写的,都被包装在rt.jar中

	//valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
	//获取偏移量,value就是构造函数中的初始值
	
	//getAndAddLong**
	return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
--------------------------------------
//getAndAddLong源码
public final long getAndAddLong(Object var1, long var2, long var4) {
	long var6;
	//自旋:内存中的值与var6一直不相等就一直循环,若未加自旋,该compareAndSwapLong返回false
	do {
		var6 = this.getLongVolatile(var1, var2);
	} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
	return var6;
}

2.原子引用类

  • 将对整个对象的操作看成原子
  • 关键加入泛型
  • AtomicReference
//compareAndSet
UserInfo userInfo = new UserInfo("Mark", 15);
userRef.set(userInfo);

UserInfo updateUser = new UserInfo("Bill", 20);
userRef.compareAndSet(userInfo, updateUser);
//注意:本身的userInfo不受影响(引用变量)
  • AtomicStampedReference
//(变量值,递增标量)--->获取(asr.getReference(),asr.getStamp())
static AtomicStampedReference<String> asr = new AtomicStampedReference<>("atomic", 0);

//oldReference更新成功就更新oldStamp
asr.compareAndSet(oldReference, oldReference + "JAVA", oldStamp, oldStamp + 1));

3.原子数组(AtomicIntegerArray)

static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);

//(索引,新值) -->原数组value内容不变(数组拷贝clone)
ai.getAndSet(0, 3);

4.LongAdder

  • 问题描述
高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低并发性
既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的
那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源就可以解决该性能问题
  • LongAdder与AtomicLong的区别
1.AtomicLong是基于CAS方式自旋更新的
	包含有原子性的读写结合的API
2.LongAdder是把value分成若干cell
	并发量低时,直接CAS更新值,成功即结束
	并发量高时,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋CAS更新 cell值
	取值时调用sum()方法进行每个cell累加
	没有原子性的读写结合的API,但能保证结果最终一致性
3.低并发场景AtomicLongLongAdder性能相似,高并发场景LongAdder性能优于AtomicLong
  • 伪共享
    在这里插入图片描述
CPU缓存系统以缓存行(cache line)为存储单位,目前主流的CPU CacheCache Line大小都是64Bytes
在多线程情况下,修改共享同一缓存行的变量会无意中影响彼此的性能

多个地址连续的变量才有可能被放到同一个缓存行中
读取数组第一个元素时,多个元素会被放到同一个缓存行
顺序访问数组元素时会在缓存行中直接命中,就不会再到主内存中读取
  • 伪共享解决(对齐)
1.填充法:为保证以下对象数据被存到同一个缓存行,如缓存行为64字节的话,基本值value占8字节,FillLong类对象的字节码的对象头占8字节,共计16字节
	64-16=48字节,所以还需要补6long型数据进去
	-----------------------------------------------
	static class FillLong {
		public volatile long value = 0L;
		public long p1, p2, p3, p4, p5, p6;
	}

2.@Contended注解,默认情况下, @Contended注解只用于java核心类,如rt.jar包下的类
	如用户类路径下的类要用此注解,需配置jvm参数: --XX:-RestrictContended
	填充宽度为128字节,要调整的话,需设置: -XX:ContendedPaddingWidth,设置@Contended注解填充变量的宽度
	此注解也可以用来修饰变量,如在LongAdder中的Cell@sun.misc.Contended static final class Cell
	----------------------------------------------
    @sun.misc.Contended
    static class FillLong2 {
        public volatile long value = 0L;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值