JUC-Atomic(原子类)

Atomic(原子类)

1、概述

JUC 原子类位于 java.util.concurrent.atomic 包下,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类

2、详情

可以将包中的类分为五类:

  • 基本类型:AtomicBoolean、AtomicInteger、AtomicLong
  • 引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference
  • 数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 对象的属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
  • JDK1.8新增:DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder

AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的原子方式访问和更新功能。例如 AtomicBoolean 提供对int类型单个变量的原子方式访问和更新功能。每个类也为该类型提供适当的实用工具方法。例如,类 AtomicLong 和 AtomicInteger 提供了原子增量方法,可以用于生成序列号。

AtomicStampedRerence 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。

AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。例如 AtomicIntegerArray 是可以用原子方式更新其元素的int数组。

AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater和AtomicLongFieldUpdater是基于反射的实用工具,可以提供对关联字段类型的访问。例如AtomicIntegerFieldUpdater可以对指定类的指定volatile int字段进行原子更新。

DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。

3、常见问题

1、原子类可以替换锁么

原子类不是锁的常规替换方法。仅当对象的重要更新限定于单个变量时才应用它。

2、原子类和java.lang.Integer等类的区别

原子类不提供诸如hashCode和compareTo之类的方法。因为原子变量是可变的。

4、原子方式更新单个变量

AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference,这几个类的共同特点是都提供单个变量的原子方式访问和更新功能。下面以AtomicLong为代表,对这些类进行介绍。

AtomicLong可以看做是用原子方式更新的long值,实例提供long类型单个变量的原子方式访问和更新功能。

1、API

1、构造函数

//创建具有初始值0的新AtomicLong。
AtomicLong() 
//创建具有给定初始值的新AtomicLong。
AtomicLong(long initialValue)     

2、方法摘要

//以原子方式将给定值添加到当前值。
long  addAndGet(long delta)

//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。         
boolean compareAndSet(long expect, long update)

//以原子方式将当前值减 1。
long  decrementAndGet()

//以 double 形式返回指定的数值。
double  doubleValue()

//以 float 形式返回指定的数值。
float  floatValue()

//获取当前值。
long  get()

//以原子方式将给定值添加到当前值。
long  getAndAdd(long delta)

//以原子方式将当前值减 1。
long  getAndDecrement()

//以原子方式将当前值加 1。
long    getAndIncrement()

//以原子方式设置为给定值,并返回旧值。
long    getAndSet(long newValue)

//以原子方式将当前值加 1。
long    incrementAndGet()

//以 int 形式返回指定的数值。
int     intValue()

//最后设置为给定值。
void    lazySet(long newValue)

//以 long 形式返回指定的数值。
long    longValue()

//设置为给定值。
void    set(long newValue)

//返回当前值的字符串表示形式。
String  toString()

//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(long expect, long update)

//将当前值以原子方式更新为updateFunction方法的结果,并返回更新前的值
long    getAndUpdate(LongUnaryOperator updateFunction)

//将当前值以原子方式更新为updateFunction方法的结果,并返回更新后的值
long    updateAndGet(LongUnaryOperator updateFunction)

//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新前的值
long    getAndAccumulate(long x,LongBinaryOperator accumulatorFunction)

//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新后的值
long    accumulateAndGet(long x,LongBinaryOperator accumulatorFunction) 

2、变量的原子访问和更新

在Java中的++i或者–i 都不是原子性的操作,一般情况下,只能加锁才能保证上述操作的原子性。有了AtomicLong后,使用AtomicLong就可以保证上述操作的原子性。

Counter是一个计数器类。(线程不安全的我们就不演示了)

class Counter {
    private static AtomicLong counter = new AtomicLong(0);

    public static long addOne() {
        return counter.incrementAndGet();
    }
}
public class AtomicLongDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (Counter.addOne() == 100) {
                        System.out.println("计数器值最终值为100");
                    }
                }
            };
            thread.start();
        }
    }
}

数次运行程序后,发现结果全部为计数器值最终值为100

3、实现原理

以incrementAndGet()为例,看看AtomicLong如何实现单个变量的原子方式更新功能。

private static final Unsafe unsafe = Unsafe.getUnsafe();
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicLong.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

Unsafe是CAS的核心类。可以看出AtomicLong是基于CAS实现的

AtomicBoolean、AtomicInteger、AtomicReference与AtomicLong很相似,就不多做介绍了

5、原子方法更新数组

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,这几个类的共同特点是都提供数组的原子方式访问和更新功能。下面以AtomicLongArray为代表,对这些类进行介绍

AtomicLongArray可以用原子方式更新其元素的long数组,实例提供long类型数组的原子方式访问和更新功能。

1、API

1、构造函数

//创建给定长度的新 AtomicLongArray。
AtomicLongArray(int length)

//创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素。  
AtomicLongArray(long[] array)

2、常用方法

//以原子方式将给定值添加到索引 i 的元素。
long addAndGet(int i, long delta)
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet(int i, long expect, long update)
//以原子方式将索引 i 的元素减1。
long  decrementAndGet(int i)
//获取位置 i 的当前值。
long  get(int i)
//以原子方式将给定值与索引 i 的元素相加。
long  getAndAdd(int i, long delta)
//以原子方式将索引 i 的元素减 1。
long  getAndDecrement(int i)
//以原子方式将索引 i 的元素加 1。
long  getAndIncrement(int i)
//以原子方式将位置 i 的元素设置为给定值,并返回旧值。
long  getAndSet(int i, long newValue)
//以原子方式将索引 i 的元素加1。         
long  incrementAndGet(int i)
//最终将位置 i 的元素设置为给定值。
void  lazySet(int i, long newValue)
//返回该数组的长度。
int   length()
//将位置 i 的元素设置为给定值。
void    set(int i, long newValue)
//返回数组当前值的字符串表示形式。
String  toString()
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(int i, long expect, long update)

//以下是JDK1.8新增的部分

//将当前值以原子方式更新为updateFunction方法的结果,并返回更新前的值
long  getAndUpdate(int i, LongUnaryOperator updateFunction)
//将当前值以原子方式更新为updateFunction方法的结果,并返回更新后的值         
long updateAndGet(int i, LongUnaryOperator updateFunction)
//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新前的值
long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction)
//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新后的值
long accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction)

2、数组的原子访问和更新

public class AtomicLongArrayDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(("[100, 100, 100, 100, 
                                100]").equals(Arrays.toString(Counter.addOne())))
                            System.out.println
                                ("计数器值最终值为[100, 100, 100, 100, 100]");
                }
            };
            thread.start();
        }
    }
}
class Counter {
    private static long[] counter = new long[]{0, 0, 0, 0, 0};

    public static long[] addOne() {
        for(int i=0;i<counter.length;i++)
            ++counter[i];
        return counter;
    }
}

测试程序在连续运行100次将数组所有元素加一的操作后,判断计数器值是否为100, 100, 100, 100, 100],如果为100, 100, 100, 100, 100]就打印计数器值最终值为100, 100, 100, 100, 100],否则就什么都不打印。

数次运行程序后,发现大多数结果是什么都没有打印,说明此计数器在多线程环境下不可用。

public class AtomicLongArrayDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (("[100, 100, 100, 100, 
                         100]").equals(Counter.addOne().toString())) {
                        System.out.println
                         ("计数器值最终值为[100, 100, 100, 100, 100]");
                    }
                }
            };
            thread.start();
        }
    }
}

class Counter {
    private static AtomicLongArray counter =
                new AtomicLongArray(new long[] {0, 0, 0, 0, 0});

    public static AtomicLongArray addOne() {
        for(int i=0;i<counter.length();i++)
            counter.incrementAndGet(i);
        return counter;
    }
}

数次运行程序后,发现结果全部为计数器值最终值为[100, 100, 100, 100, 100]

3、实现原理

与AtomicLong相同,AtomicLong也是基于CAS实现的。

AtomicIntegerArray、AtomicReferenceArray与AtomicLongArray很相似,就不多做介绍了。

6、原子方式更新引用

AtomicReference 类提供了可以以原子方式读取和写入的引用对象,因此当多个线程同时试图到达它们时,只有一个线程能够这样做。

即:提供了引用变量的读写原子性操作。

1、API

  • compareAndSet(V expect, V update):如果当前值等于预期值,则原子性地将值设置为给定的更新值
  • getAndSet(V newValue):原子性地设置为给定的值并返回旧值。
  • lazySet(V newValue): 最终设置为给定的值。
  • set(V newValue):设置为给定的值。
  • get():获取当前的值

2、引用的原子访问和更新

假设有一个类 Person,定义如下:

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;

        get/set/toString
        ......

}

如果使用普通的对象引用,在多线程情况下进行对象的更新可能会导致不一致性。例如:

  1. 一个对象的初始状态为 name=Tom, age = 18。
  2. 在 线程1 中将 name 修改为 Tom1,age + 1。
  3. 在 线程2 中将 name 修改为 Tom2,age + 2。
// 普通引用
private static Person person;

public static void main(String[] args) throws Exception {

    person = new Person("Tom", 18);
    System.out.println("Person is " + person.toString());

    Thread t1 = new Thread(new Task1());
    Thread t2 = new Thread(new Task2());

    t1.start();
    t2.start();

    //保证t1、t2执行完了再执行主线程
    t1.join();
    t2.join();

    System.out.println("Now Person is " + person.toString());
}

static class Task1 implements Runnable {
    public void run() {
        person.setAge(person.getAge() + 1);
        person.setName("Tom1");

        System.out.println("Thread1 Values "+ person.toString());
    }
}

static class Task2 implements Runnable {
    public void run() {
        person.setAge(person.getAge() + 2);
        person.setName("Tom2");

        System.out.println("Thread2 Values "+ person.toString());
    }
}

我们自己认为只会产生两种结果:

  • 若 线程1 先执行,线程2 后执行,则中间状态为 name=Tom1, age = 19,结果状态为 name=Tom2, age = 21
  • 若 线程2 先执行,线程1 后执行,则中间状态为 name=Tom2, age = 20,结果状态为 name=Tom1, age = 21

但是可能的输出如下:

Person is [name: Tom, age: 18]
Thread2 Values [name: Tom1, age: 21]
Thread1 Values [name: Tom1, age: 21]
Now Person is [name: Tom1, age: 21]

因为不能保证修改引用对象的属性时候的原子性

果使用原子性对象引用,在多线程情况下进行对象的更新可以确保一致性(符合我们上述的预期)。例如:

// 普通引用
private static Person person;
// 原子性引用
private static AtomicReference<Person> aRperson;

public static void main(String[] args) throws InterruptedException {
    person = new Person("Tom", 18);
    aRperson = new AtomicReference<Person>(person);

    System.out.println("Atomic Person is " + aRperson.get().toString());

    Thread t1 = new Thread(new Task1());
    Thread t2 = new Thread(new Task2());

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println("Now Atomic Person is " + aRperson.get().toString());
}

static class Task1 implements Runnable {
    public void run() {
        aRperson.getAndSet(new Person("Tom1", aRperson.get().getAge() + 1));

        System.out.println("Thread1 Atomic References "
                + aRperson.get().toString());
    }
}

static class Task2 implements Runnable {
    public void run() {
        aRperson.getAndSet(new Person("Tom2", aRperson.get().getAge() + 2));

        System.out.println("Thread2 Atomic References "
                + aRperson.get().toString());
    }
}

可能的输出如下(符合预期):

Atomic Person is [name: Tom, age: 18]
Thread1 Atomic References [name: Tom1, age: 19]
Thread2 Atomic References [name: Tom2, age: 21]
Now Atomic Person is [name: Tom2, age: 21]

7、原子方式更新类的指定volatile字段

AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater 和 AtomicLongFieldUpdater 是基于反射的实用工具,可以提供对关联字段类型的原子访问。例如 AtomicLongFieldUpdater 可以对指定类的指定volatile long字段进行原子更新。本文以AtomicLongFieldUpdater为例来学习。

1、API

1、构造函数

//受保护的无操作构造方法,供子类使用。
protected   AtomicLongFieldUpdater() 

2、方法概述

//以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long  addAndGet(T obj, long delta) 

//如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract  boolean  compareAndSet(T obj, long expect, long update) 

//以原子方式将此更新器管理的给定对象字段当前值减 1。
long   decrementAndGet(T obj) 

//获取此更新器管理的在给定对象的字段中保持的当前值。
abstract  long get(T obj) 

//以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long   getAndAdd(T obj, long delta) 

//以原子方式将此更新器管理的给定对象字段当前值减 1。
long   getAndDecrement(T obj) 

//以原子方式将此更新器管理的给定对象字段的当前值加 1。
long   getAndIncrement(T obj) 

//将此更新器管理的给定对象的字段以原子方式设置为给定值,并返回旧值。
long   getAndSet(T obj, long newValue) 

//以原子方式将此更新器管理的给定对象字段当前值加 1。
long   incrementAndGet(T obj) 

//最后将此更新器管理的给定对象的字段设置为给定更新值。
abstract  void  lazySet(T obj, long newValue) 

//为对象创建并返回一个具有给定字段的更新器。
static
<U> AtomicLongFieldUpdater<U>  newUpdater(Class<U> tclass, String fieldName) 

//将此更新器管理的给定对象的字段设置为给定更新值。
abstract  void  set(T obj, long newValue) 

//如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract  boolean   weakCompareAndSet(T obj, long expect, long update) 

//以下是JDK1.8新增的部分

//将当前值以原子方式更新为updateFunction方法的结果,并返回更新前的值
long    getAndUpdate(int i, LongUnaryOperator updateFunction)

//将当前值以原子方式更新为updateFunction方法的结果,并返回更新后的值
long updateAndGet(int i, LongUnaryOperator updateFunction)

//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新前的值
long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction)

//将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新后的值
long accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction)

通常情况下,在Java中的++i或者-–i不是线程安全的。一般情况下,只能加锁才能保证上述操作的原子性。有了AtomicLongFieldUpdater后,使用AtomicLongFieldUpdater就可以保证上述操作的原子性。

2、原子方式更新类的指定volatile字段

Counter是一个计数器类。

class Counter extends Thread {
    private static long counter = 0;

    public static long addOne() {
        return ++counter;
    }
}

在多线程环境下测试其是否可用。

public class CounterTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (Counter.addOne() == 100) {
                        System.out.println("计数器值最终值为100");
                    }
                }
            };
            thread.start();
        }
    }
}

测试程序,在连续运行100次++counter后,判断计数器值是否为100,如果为100就打印计数器值最终值为100,否则就什么都不打印。
数次运行程序后,发现大多数结果是什么都没有打印,说明次计数器在多线程环境下不可用。
修改计数器类。

class Counter {
    private volatile long counter = 0;

    static AtomicLongFieldUpdater updater = 
        AtomicLongFieldUpdater.newUpdater(Counter.class, "counter");
    static Counter safeCounter = new Counter ();

    public static long addOne() {
        return updater.addAndGet(safeCounter, 1);
    }
}

在多线程环境下测试其是否可用。数次运行程序后,发现结果全部为计数器值最终值为100。

3、原理

与其他原子类一样,AtomicLongFieldUpdater也是基于CAS实现的。

AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater与AtomicLongFieldUpdater很相似,就不多做介绍了。

8、JDK8 新增的原子类

DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是对AtomicLong等类的改进。比如 LongAccumulator 与LongAdder在高并发环境下比 AtomicLong更高效。本文以LongAdder为例,学习这些类。

API中是这么介绍的:LongAdder 中会维护一组(一个或多个)变量,这些变量加起来就是要以原子方式更新的long型变量。当更新方法add(long)在线程间竞争时,该组变量可以动态增长以减缓竞争。方法sum()返回当前在维持总和的变量上的总和。与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。在低并发环境下,两者性能很相似。但在高并发环境下,LongAdder有着明显更高的吞吐量,但是有着更高的空间复杂度。

1、API

//构造函数
LongAdder()
    //创建初始和为零的新加法器。

//方法摘要
void    add(long x)
    //添加给定的值。
void    decrement()
    //相当于add(-1)。
double  doubleValue()
    //在扩展原始转换之后返回sum()as double。
float   floatValue()
    //在扩展原始转换之后返回sum()as float。
void    increment()
    //相当于add(1)。
int intValue()
    //返回sum()作为int一个基本收缩转换之后。
long    longValue()
    //相当于sum()。
void    reset()
    //重置将总和保持为零的变量。
long    sum()
    //返回当前的总和。
long    sumThenReset()
    //等同于sum()后面的效果reset()。
String  toString()
    //返回。的字符串表示形式sum()。123456789101112131415161718192021222324252627

2、使用新增的原子类变量的原子访问和更新

class Counter {
    private static LongAdder counter = new LongAdder();

    public static long addOne() {
        counter.add(1);
        return counter.sum();
    }
}

数次运行例1:long型变量的原子访问和更新中的测试程序,,发现结果全部为计数器值最终值为100。说明说明次计数器在多线程环境下可用,LongAdder实现long型变量的原子访问和更新。

实现原理
与其他原子类一样,LongAdder也是基于CAS实现的。

LongAdder可以代替AtomicLong吗

当然不能。在上面已经提到,与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。而且,LongAdder只提供了add(long)和decrement()方法,想要使用cas方法还是要选择AtomicLong。

DoubleAdder、LongAccumulator、DoubleAccumulator与LongAdder很相似,就不多做介绍了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值