java高并发高级篇之原子类与不可变对象

前言

本文主要介绍下原子类与不可变对象的概念,在介绍下java中的原子类。
他们内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile(保证了可见性)和native方法,从而避免了synchronized的高开销,执行效率大为提升。

CAS 算法的过程是这样:

它包含 3 个参数CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS 返回当前 V 的真实值。

CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

ABA 问题问题的产生:

CAS 会导致“ABA 问题”。CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。

部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。

一、简单数据类型

AtomicBoolean         布尔类型
AtomicInteger          整数
AtomicLong            长整数
AtomicReference        对象

虽然原子的标量类扩展了基本类型的类,但是并没有扩展基本类型的包装类,如Integer或Long,事实上它们也不能直接扩展。因为基
本类型的包装类是不可以修改的,而原子变量类是可以修改的。在原子变量类中没有重新定义hashCode或equals方法,每个实例都是
不同的,他们也不宜用做基于散列容器中的键值,因为有可能造成数据混乱,如下

AtomicInteger atomicInteger1 = new AtomicInteger(1);
map.put(atomicInteger1,1);
System.out.println(map.get(atomicInteger1));//1

AtomicInteger atomicInteger2 = new AtomicInteger(1);
map.put(atomicInteger2,2);
System.out.println(map.get(atomicInteger2));//2

atomicInteger1.set(100);
System.out.println(map.get(atomicInteger1));//1

从这里可以看出AtomicInteger可以作为散列容器中的键值,只是容易造成数据混乱。键都是new AtomicInteger(1),怎么值不一样?嗯。。可以认为把AtomicInteger这类没有重新定义hashCode或equals的原子类作为散列容器中的键值是一种坏习惯。

这里在多说一下,将可变对象作为散列容器中的键值是错误的 。

public  class Person{
        private int age;
        //忽略get和set方法
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return age == person.age;
        }
        @Override
        public int hashCode() {
            return Objects.hash(age);
        }
    }
    public static void main(String[] args) {
        Map<Person,Integer> map = new HashMap<>();
        Person person = new Person();
        map.put(person,1);
        System.out.println(map.get(person));//1
        person.setAge(100);
        System.out.println(map.get(person));//null
    }

可以看我第一次储存的数据“消失”了,原因:HashMap提供key的hashCode和equals方法取值的

1.1 构造方法
AtomicBooleanAtomicIntegerAtomicLongAtomicReference<V>
AtomicBoolean()AtomicInteger()AtomicLong()AtomicReference()
AtomicBoolean(boolean initialValue)AtomicInteger(int initialValue)AtomicLong(long initialValue)AtomicReference(V initialValue)
1.2 直接获取当前值
AtomicBooleanAtomicIntegerAtomicLongAtomicReference<V>
boolean get()int get()long get()V get()
1.3 先获取当前旧值,在执行赋值动作
AtomicIntegerAtomicLong
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction)
int getAndAdd(int delta)long getAndAdd(long delta)
int getAndDecrement()long getAndDecrement()
int getAndIncrement()long getAndIncrement()
int getAndSet(int newValue)long getAndSet(long newValue)
int getAndUpdate(IntUnaryOperator updateFunction)long getAndUpdate(LongUnaryOperator updateFunction)
AtomicBooleanAtomicReference<V>
boolean getAndSet(boolean newValue)V getAndSet(V newValue)
‘’V getAndUpdate(UnaryOperator updateFunction)
‘’V getAndAccumulate(V x, BinaryOperator accumulatorFunction)
1.4 先执行动作,在获取新值
AtomicIntegerAtomicLong
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction)
int decrementAndGet()long decrementAndGet()
int addAndGet(int delta)long addAndGet(long delta)
int incrementAndGet()long incrementAndGet()
int updateAndGet(IntUnaryOperator updateFunction)long updateAndGet(LongUnaryOperator updateFunction)
AtomicBooleanAtomicReference
V accumulateAndGet(V x, BinaryOperator accumulatorFunction)V updateAndGet(UnaryOperator updateFunction)
1.5 设置值,无返回值
AtomicBooleanAtomicIntegerAtomicLongAtomicReference<V>
void lazySet(boolean newValue)void lazySet(int newValue)void lazySet(long newValue)void lazySet(V newValue)
void set(boolean newValue))void set(int newValue)void set(long newValue)void set(V newValue)

set设置为给定值,直接修改原始值;
lazySet延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。

1.6 设置值,有返回值,如果true表示设置成功

这2个方法他们4个都有

boolean   weakCompareAndSet( 期望的值, 新值) 
boolean   compareAndSet(期望的值, 新值)

他底层使用了CAS算法,这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一 致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。

二、数组类

AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray

进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供

volatile访问语义方面也引人注目,这对于普通数组来说是不受支持的。其内部并不是像AtomicInteger一样维持一个volatile变量,而是全部由native方法实现。

数组变量进行volatile没有意义,因此set/get就需要unsafe来做了,但是多了一个index来指定操作数组中的哪一个元素。

2.1 构造函数
AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray<E>
AtomicIntegerArray(int length)AtomicLongArray(int length)AtomicReferenceArray(int length)
AtomicIntegerArray(int[] array)AtomicLongArray(long[] array)AtomicReferenceArray(E[] array)
2.2 直接获取当前索引的值
AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray<E>
int get(int i)long get(int i)E get(int i)
2.3 直接设置当前索引的值,有返回值

三个数组类都有

boolean compareAndSet(int i, 比较值, 更新值)
boolean weakCompareAndSet(int i, 比较值, 更新值)

2.4 先设置,在获取

AtomicIntegerArray

int  addAndGet(int i, int delta)

int  decrementAndGet(int i)

int  incrementAndGet(int i)

int  accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)

int  updateAndGet(int i, IntUnaryOperator updateFunction)

AtomicLongArray

long  addAndGet(int i, long delta)

long  decrementAndGet(int i)

long  incrementAndGet(int i)

long  accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction)

long  updateAndGet(int i, LongUnaryOperator updateFunction)

AtomicReferenceArray

E  accumulateAndGet(int i, E x, BinaryOperator<E> accumulatorFunction)

E  updateAndGet(int i, UnaryOperator<E> updateFunction)
2.5 先获取,在设置

AtomicIntegerArray

int  getAndAdd(int i, int delta)

int  getAndDecrement(int i)

int  getAndIncrement(int i)

int  getAndSet(int i, int newValue)

int  getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction)

int  getAndUpdate(int i, IntUnaryOperator updateFunction)

AtomicLongArray

long   getAndAdd(int i, long delta)

long   getAndDecrement(int i)

long   getAndIncrement(int i)

long   getAndSet(int i, long newValue)

long   getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction)

long   getAndUpdate(int i, LongUnaryOperator updateFunction)

AtomicReferenceArray

E   getAndSet(int i, E newValue)

E   getAndAccumulate(int i, E x, BinaryOperator<E> accumulatorFunction)

E   getAndUpdate(int i, UnaryOperator<E> updateFunction)
2.6 直接设置当前索引的值,无返回值
AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray<E>
void lazySet(int i, int newValue)void lazySet(int i, long newValue)void lazySet(int i, E newValue)
void set(int i, int newValue)void set(int i, long newValue)void set(int i, E newValue)
2.7 获取数组长度

三个数组类都有

int    length()

三、统计类

DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder

它们都是继承java.lang.Number

举例说明:从LongAdder的Api可以看出,提供的方法还是挺少的。它更多地用于收集统计数据,而不是细粒度的同步控制。

LongAdder只提供了add(long)和decrement()方法,想要使用CAS更全面的方法还是要选择AtomicLong。

因此如果你只需要做形如count++的操作,推荐使用LongAdder代替AtomicLong吧(阿里开发手册就是这么推荐的)

3.1 构造方法

XXAccumulator是自定义统计器,而XXAdder是加法统计器

DoubleAccumulatorDoubleAdder
DoubleAccumulator(DoubleBinaryOperator accumulatorFunction, double identity)DoubleAdder()
LongAccumulatorLongAdder
LongAccumulator(LongBinaryOperator accumulatorFunction, long identity)LongAdder()
3.2 创建一个新的加法器,初始和为零
DoubleAccumulatorDoubleAdder作用
void accumulate(double x)void accumulate(long x)在cell数组中添加一个值
double get()long get()把cell数组中的数按照统计器计算,然后获取值
double getThenReset()long getThenReset()把cell数组中的数按照统计器计算,然后获取值,在初始化
void reset()void reset()初始化cell数组
DoubleAdderLongAdder作用
void add(double x)void add(long x)在cell数组中添加一个值
double get()void decrement()在cell数组中添加一个值:值是-1
double getThenReset()void increment()在cell数组中添加一个值:值是1
void reset()void reset()初始化cell数组
double sum()long sum()求和cell数组
double sumThenReset()long sumThenReset()先求和c,在初始化

四、更新器类

它们都是抽象类

AtomicLongFieldUpdater
AtomicIntegerFieldUpdater
AtomicReferenceFieldUpdater

他们都是基于反射的实用工具,可以提供对关联字段类型的访问,可用于获取任意选定volatile字段上的compareAndSet操作。它们主要用于原子数据结构中,该结构中同一节点的几个 volatile 字段都独立受原子更新控制。这些类在如何以及何时使用原子更新方面具有更大的灵活性,但相应的弊端是基于映射的设置较为拙笨、使用不太方便,而且在保证方面也较差。
使用中要注意一下几点:

(1)字段必须是volatile类型的:底层使用cas算法,cas算法不能保证字段的共享性。而volatile可以

(2)在本类中字段的描述类型没有限制,而对于外部类,字段修饰符只能是public/protected/default

(3)但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(4)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(5)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(6)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。
如果要修改包装类型就需要使用AtomicReferenceFieldUpdater 。

4.1 获取更新器类对象

不能更新private 修饰的类

1、AtomicReferenceFieldUpdater<U,W> U - 持有可更新字段的对象的类型 W - 字段的类型

static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater( Class <U> tclass, Class <W> vclass, String fieldName)

2、AtomicLongFieldUpdater<U> U 持有可更新字段的对象的类型

static <U> AtomicLongFieldUpdater<U>   newUpdater(Class <U> tclass, String fieldName)

3、AtomicIntegerFieldUpdater<U> U -持有可更新字段的对象的类型

static <U> AtomicIntegerFieldUpdater<U>  newUpdater(Class <U> tclass, String fieldName)
4.2 先设置,在获取值

方法说明:obj是要获取和设置的字段的对象

1、AtomicReferenceFieldUpdater<U,W> U - 持有可更新字段的对象的类型 W - 字段的类型

V  accumulateAndGet(T obj, V x, BinaryOperator<V> accumulatorFunction)
以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值
V  updateAndGet(T obj, UnaryOperator<V> updateFunction)
以原子方式更新一个值后获取值

2、AtomicLongFieldUpdater<U> U -持有可更新字段的对象的类型

long updateAndGet(T obj, LongUnaryOperator updateFunction)
以原子方式更新一个值后获取值
long incrementAndGet(T obj)
以原子方式自增获取后获取值
long decrementAndGet(T obj)
以原子方式自减获取后获取值
long addAndGet(T obj, long delta)
以原子方式增加一个值后获取值
long  accumulateAndGet(T obj, long x, LongBinaryOperator accumulatorFunction)
以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值

3、AtomicIntegerFieldUpdater<U> U -持有可更新字段的对象的类型

int  incrementAndGet(T obj)
以原子方式自增获取后获取值
int  decrementAndGet(T obj)
以原子方式自减获取后获取值
int  addAndGet(T obj, int delta)
以原子方式增加一个值后获取值
int  accumulateAndGet(T obj, int x, IntBinaryOperator accumulatorFunction)
以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值。
int  updateAndGet(T obj, IntUnaryOperator updateFunction)
以原子方式更新一个值后获取值
4.3 先获取值,在设置

1、AtomicReferenceFieldUpdater<U,W> U - 持有可更新字段的对象的类型 W - 字段的类型

V  getAndAccumulate(T obj, V x, BinaryOperator<V> accumulatorFunction)
以原子方式先获值,在用给定的方法对当前及给定的值进行更新
V  getAndUpdate(T obj, UnaryOperator<V> updateFunction)
以原子方式先获值,在更新值
V  getAndSet(T obj, V newValue)
以原子方式先获值,在更新值

2、AtomicLongFieldUpdater<U> U -持有可更新字段的对象的类型

long getAndAccumulate(T obj, long x, LongBinaryOperator accumulatorFunction)
以原子方式先获值,在用给定的方法对当前及给定的值进行更新
long getAndAdd(T obj, long delta)
以原子方式先获值,在增加一个值
long getAndDecrement(T obj)
以原子方式先获值,在自减
long getAndIncrement(T obj)
以原子方式先获值,在自增
long getAndSet(T obj, long newValue)
以原子方式先获值,在更新值
long getAndUpdate(T obj, LongUnaryOperator updateFunction)
以原子方式先获值,在更新值

3、AtomicIntegerFieldUpdater<U> U -持有可更新字段的对象的类型

int  getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction)
以原子方式先获值,在用给定的方法对当前及给定的值进行更新
int  getAndAdd(T obj, int delta)
以原子方式先获值,在增加一个值
int  getAndDecrement(T obj)
原子方式先获值,在自减
int  getAndIncrement(T obj)
以原子方式先获值,在自增
int  getAndSet(T obj, int newValue)
以原子方式先获值,在更新值
int  getAndUpdate(T obj, IntUnaryOperator updateFunction)
以原子方式先获值,在更新值
4.4 直接获取
AtomicReferenceFieldUpdater<U,W>AtomicLongFieldUpdater<U>AtomicIntegerFieldUpdater<U>
abstract V get(T obj)abstract long get(T obj)abstract int get(T obj)
4.5 直接设置,有返回设置是否成功
abstract boolean   compareAndSet(T obj, V expect, V update)
abstract boolean   weakCompareAndSet(T obj, long expect, long update)
obj 作用对象        expect:期望的值      update:要更新的值
如果期望值等于原来的值,那么就更新成功,返回true
4.6 直接设置,无返回值
AtomicReferenceFieldUpdater<U,W>AtomicLongFieldUpdater<U>AtomicIntegerFieldUpdater<U>
void lazySet(T obj, V newValue)void lazySet(T obj, long newValue)void lazySet(T obj, int newValue)
void set(T obj, V newValue)void set(T obj, long newValue)void set(T obj, int newValue)
4.7 案例
class  Person {
	volatile  String name="老刘";  
}

AtomicReferenceFieldUpdater updater= AtomicReferenceFieldUpdater.newUpdater(Person .class, String.class, "name");  
Person person = new Person;
updater.set(person ,"老王");  //这是原子操作
System.out.println(person.name); 		

五、复合变量类

AtomicMarkableReference<V> 类将单个布尔值与引用关联起来。维护带有标记位的对象引用,可以原子方式更新带有标记位的引用类型。

AtomicStampedReference<V> 类将整数值与引用关联起来。维护带有整数“标志”的对象引用,可以原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

5.1 构造方法
AtomicStampedReferenceAtomicMarkableReference
AtomicStampedReference(V initialRef, int initialStamp)AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicStampedReference可以知道,引用变量中途被更改了几次。

有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference(不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过)。

5.2 设置值,如果版本和时间戳相同,那么设置成功

AtomicStampedReference

boolean  weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
boolean  compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
expectedReference:假设原来的值
newReference:新值
expectedStamp:假设原来的时间戳
newStamp:新值
Person person1 =   new Person(11);
Person person2 =   new Person(12);
AtomicStampedReference<Person> atomicStampedReference1 = new AtomicStampedReference<>(person1,1);
atomicStampedReference1.compareAndSet(person1,person2,1,2);
//设置成功
System.out.println(atomicStampedReference1.getStamp());//2
//设置成功失败
atomicStampedReference1.compareAndSet(person1,person2,1,3);
System.out.println(atomicStampedReference1.getStamp());//2

AtomicMarkableReference

boolean    weakCompareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark)
boolean    compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark)
Person person1 =   new Person(11);
Person person2 =   new Person(12);
AtomicMarkableReference<Person> atomicMarkableReference = new AtomicMarkableReference<>(person1,false);
atomicMarkableReference.compareAndSet(person1,person2,false,true);
System.out.println(atomicMarkableReference.isMarked());//true
atomicMarkableReference.compareAndSet(person2,person1,false,true);
System.out.println(atomicMarkableReference.isMarked());//true
5.3 设置值,直接设置(暴力直接设置版本和值)
AtomicStampedReferenceAtomicMarkableReference
void set(V newReference, int newStamp)void set(V newReference, boolean newMark)
5.4 设置值,只修改版本,不修改值

比较expectedReference是否和旧值是否相等,如果相等那么就设置新版本newStamp成功,否则失败

AtomicStampedReferenceAtomicMarkableReference
boolean attemptStamp(V expectedReference, int newStamp)boolean attemptMark(V expectedReference, boolean newMark)
5.5 获取值
AtomicStampedReferenceAtomicMarkableReference
V get(int[] stampHolder)V get(boolean[] markHolder)
V getReference()V getReference()
Person person1 =   new Person(11);
Person person2 =   new Person(12);
AtomicMarkableReference<Person> atomicMarkableReference = new AtomicMarkableReference<>(person1,false);
boolean[] d = {false,false};
System.out.println(atomicMarkableReference.get(d).getAge());//11
System.out.println(Arrays.toString(d));//[true, false] 获取当前对象的同时,会改变数组[0]的值
System.out.println(atomicMarkableReference.getReference().getAge());//11
5.6 获取时间戳
AtomicStampedReferenceAtomicMarkableReference
int getStamp()boolean isMarked()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值