java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理
Posted halulu.me
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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指令)。
以上是关于java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理的主要内容,如果未能解决你的问题,请参考以下文章
java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理
java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理