基础篇:JAVA原子组件和同步组件

在使用多线程并发编程的时,经常会遇到对共享变量修改操作。此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据。但如果单单是涉及状态的修改,线程执行顺序问题,使用Atomic开头的原子组件或者ReentrantLock、CyclicBarrier之类的同步组件,会是更好的选择,下面将一一介绍它们的原理和用法

原子组件的实现原理CAS

AtomicBoolean、AtomicIntegerArray等原子组件的用法、

同步组件的实现原理

ReentrantLock、CyclicBarrier等同步组件的用法

关注公众号,一起交流,微信搜一搜: 潜行前行 原子组件的实现原理CAS

cas的底层实现可以看下之前写的一篇文章:详解锁原理,synchronized、volatile+cas底层实现

应用场景

可用来实现变量、状态在多线程下的原子性操作

可用于实现同步锁(ReentrantLock)

原子组件

原子组件的原子性操作是靠使用cas来自旋操作volatile变量实现的

volatile的类型变量保证变量被修改时,其他线程都能看到最新的值

cas则保证value的修改操作是原子性的,不会被中断

基本类型原子类 AtomicBoolean //布尔类型 AtomicInteger //正整型数类型 AtomicLong //长整型类型

使用示例

public static void main(String[] args) throws Exception { AtomicBoolean atomicBoolean = new AtomicBoolean(false); //异步线程修改atomicBoolean CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{ try { Thread.sleep(1000); //保证异步线程是在主线程之后修改atomicBoolean为false atomicBoolean.set(false); }catch (Exception e){ throw new RuntimeException(e); } }); atomicBoolean.set(true); future.join(); System.out.println("boolean value is:"+atomicBoolean.get()); } ---------------输出结果------------------ boolean value is:false 引用类原子类 AtomicReference //加时间戳版本的引用类原子类 AtomicStampedReference //相当于AtomicStampedReference,AtomicMarkableReference关心的是 //变量是否还是原来变量,中间被修改过也无所谓 AtomicMarkableReference

AtomicReference的源码如下,它内部定义了一个volatile V value,并借助VarHandle(具体子类是FieldInstanceReadWrite)实现原子操作,MethodHandles会帮忙计算value在类的偏移位置,最后在VarHandle调用Unsafe.public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x)方法原子修改对象的属性

public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; private static final VarHandle VALUE; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } private volatile V value; .... ABA问题

线程X准备将变量的值从A改为B,然而这期间线程Y将变量的值从A改为C,然后再改为A;最后线程X检测变量值是A,并置换为B。但实际上,A已经不再是原来的A了

解决方法,是把变量定为唯一类型。值可以加上版本号,或者时间戳。如加上版本号,线程Y的修改变为A1->B2->A3,此时线程X再更新则可以判断出A1不等于A3

AtomicStampedReference的实现和AtomicReference差不多,不过它原子修改的变量是volatile Pair<V> pair;,Pair是其内部类。AtomicStampedReference可以用来解决ABA问题

public class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair;

如果我们不关心变量在中间过程是否被修改过,而只是关心当前变量是否还是原先的变量,则可以使用AtomicMarkableReference

AtomicStampedReference的使用示例

public class Main { public static void main(String[] args) throws Exception { Test old = new Test("hello"), newTest = new Test("world"); AtomicStampedReference<Test> reference = new AtomicStampedReference<>(old, 1); reference.compareAndSet(old, newTest,1,2); System.out.println("对象:"+reference.getReference().name+";版本号:"+reference.getStamp()); } } class Test{ Test(String name){ this.name = name; } public String name; } ---------------输出结果------------------ 对象:world;版本号:2 数组原子类 AtomicIntegerArray  //整型数组 AtomicLongArray  //长整型数组 AtomicReferenceArray //引用类型数组

数组原子类内部会初始一个final的数组,它把整个数组当做一个对象,然后根据下标index计算法元素偏移量,再调用UNSAFE.compareAndSetReference进行原子操作。数组并没被volatile修饰,为了保证元素类型在不同线程的可见,获取元素使用到了UNSAFEpublic native Object getReferenceVolatile(Object o, long offset)方法来获取实时的元素值

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwgps.html