java并发编程 原子类(atomic包)

一、 原子操作类介绍

在并发编程中很容易出现并发安全的问题,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在java.util.concurrent包下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。

二、 预备知识--CAS算法

能够弄懂atomic包下这些原子操作类的实现原理,就要先明白什么是CAS算法。
什么是CAS?
使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS机制(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

CAS的实现需要硬件指令集的支撑,使得检测和交换作为原子操作。

Synchronized VS CAS
元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。
使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。

三、原子类的分类

原子更新基本类型(3个)
AtomicBoolean 原子更新布尔类型
AtomicInteger 原子更新整型
AtomicLong 原子更新长整型
原子更新数组(3个)
AtomicIntegerArray 原子更新整形数组中的元素
AtomicLongArray 原子更新长整型数组中的元素
AtomicReferenceArray 原子更新引用类型数组中的元素
原子更新引用类型(3个)
AtomicReference 原子更新引用类型
AtomicMarkableReference 原子更新带有标记位的引用类型
AtomicStampedReference 原子更新带有版本号的引用类型,。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题
原子更新字段类(3个)
AtomicIntegerFieldUpdater 原子更新整形字段
AtomicLongFieldUpdater 原子更新长整型字段
AtomicReferenceFieldUpdater 原子更新引用类型字段

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

四、原子更新基本类型

使用原子方式更新基本类型,共包括3个类:

AtomicBoolean:原子更新布尔变量
AtomicInteger:原子更新整型变量
AtomicLong:原子更新长整型变量
常用的方法如下,以AtomicInteger源码为例进行说明(JDK 1.7),其他2个类似:

public class AtomicInteger extends Number implements java.io.Serializable {


    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

    //返回当前值     
    public final int get() {
        return value;
    }

    //设置新的值,非原子操作
    public final void set(int newValue) {
        value = newValue;
    }

   //最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }

    //原子操作,取当前的值,并设置新的值,返回旧值
    public final int getAndSet(int newValue) {
        for (;;) {                     //自旋
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }

    //如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }   //unsafe类提供的是硬件级别的方法,是CSA原语,即检测和更新是原子操作

    //如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    //以原子方式将当前值加 1,返回旧值
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

   //以原子方式将当前值减 1,返回旧值
    public final int getAndDecrement() {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

    //以原子方式将给定值与当前值相加,返回旧值
    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    }

    //以原子方式将当前值加 1。
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    //以原子方式将当前值减 1,返回新值
    public final int decrementAndGet() {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    //以原子方式将给定值与当前值相加,返回新值
    public final int addAndGet(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return next;
        }
    }
}

其中LazySet()方法比较特殊,关于该方法的更多信息可以参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》

使用例子
多线程情况下,对AtomicInteger的原子自增操作。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void increase(){
        atomicInteger.incrementAndGet();
    }       
    public static void main(String[] args){
        for (int i = 0; i < 5; i++){
            new Thread(new Runnable() {
                public void run() {
                    for(int j=0;j<1000;j++)
                        increase();
                }
            }).start();
        }
        while(Thread.activeCount()>1)
            Thread.yield();
        System.out.println(atomicInteger.get());
    }
}

在多线程的情况下,得到的结果是正确的5*1000,但是如果仅仅使用int类型的成员变量则可能得到不同的结果。这里的关键在于getAndIncrement是原子操作compareAndSet(current, next)。

五、原子更新数组

通过原子更新数组里的某个元素,共有3个类:

AtomicIntegerArray:原子更新整型数组的某个元素
AtomicLongArray:原子更新长整型数组的某个元素
AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray为例讲解,其他2和类似,部分源码如下(JDK 1.7):

public class AtomicIntegerArray implements java.io.Serializable {

    private final int[] array;

    /**
     * Creates a new AtomicIntegerArray of the given length, with all
     * elements initially zero.
     *
     * @param length the length of the array
     */
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

    /**
     * Creates a new AtomicIntegerArray with the same length as, and
     * all elements copied from, the given array.
     *
     * @param array the array to copy elements from
     * @throws NullPointerException if array is null
     */
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }

    /**
     * Returns the length of the array.
     *
     * @return the length of the array
     */
    public final int length() {
        return array.length;
    }

    /**
     * Gets the current value at position {@code i}.
     *
     * @param i the index
     * @return the current value
     */
    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

    /**
     * Sets the element at position {@code i} to the given value.
     *
     * @param i the index
     * @param newValue the new value
     */
    public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }

    /**
     * Eventually sets the element at position {@code i} to the given value.
     *
     * @param i the index
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }

    /**
     * Atomically sets the element at position {@code i} to the given
     * value and returns the old value.
     *
     * @param i the index
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int i, int newValue) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, newValue))
                return current;
        }
    }

    /**
     * Atomically sets the element at position {@code i} to the given
     * updated value if the current value {@code ==} the expected value.
     *
     * @param i the index
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int i, int expect, int update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
    }

    private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
    }
}

使用例子:
多线程情况下,对数组的每个值都自增,这里使用Executor框架管理线程,代码如下:

 public class AtomicIntegerArrayDemo {  
02    staticAtomicIntegerArray arr = new AtomicIntegerArray(10);  
03     public staticclass AddThread implements Runnable{  
04         publicvoid run(){  
05            for(intk=0;k<10000;k++)  
06                 arr.getAndIncrement(k%arr.length());  
07         }  
08     }  
09    public staticvoid main(String[] args) throws InterruptedException {  
10         Thread[]ts=new Thread[10];  
11         for(intk=0;k<10;k++){  
12            ts[k]=new Thread(new AddThread());  
13         }  
14         for(intk=0;k<10;k++){ts[k].start();}  
15         for(intk=0;k<10;k++){ts[k].join();}  
16         System.out.println(arr);  
17    }  
18 }  

上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操作,每个元素各加1000次。第11行,开启10个这样的线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是10000。反之,如果线程不安全,则部分或者全部数值会小于10000。

程序的输出结果如下:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000,10000, 10000]

这说明AtomicIntegerArray确实合理地保证了数组的线程安全性。

六、原子更新引用类型

需要更新引用类型往往涉及多个变量,早atomic包有三个类:
AtomicReference 原子更新引用类型
AtomicMarkableReference 原子更新带有标记位的引用类型
AtomicStampedReference 原子更新带有版本号的引用类型,。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题
下面以AtomicReference为例进行说明:
提供了如下的方法:

compareAndSet(V expect, V update):Atomically sets the value to the given updated value if the current value == the expected value.

getAndSet(V newValue):Atomically sets to the given value and returns the old value.

lazySet(V newValue):Eventually sets to the given value.

set(V newValue):Sets to the given value.

get():Gets the current value.

class Person {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "[name: " + this.name + ", age: " + this.age + "]";
    }
}

如果使用普通的对象引用,例如:
一个对象的初始状态为 name=Tom, age = 18。
在 线程1 中将 name 修改为 Tom1,age + 1。
在 线程2 中将 name 修改为 Tom2,age + 2。
我们认为只会产生两种结果:
若 线程1 先执行,线程2 后执行,则中间状态为 name=Tom1, age = 19,结果状态为 name=Tom2, age = 21
若 线程2 先执行,线程1 后执行,则中间状态为 name=Tom2, age = 20,结果状态为 name=Tom1, age = 21
在多线程情况下进行对象的更新可能会导致不一致性。

// 普通引用
private static Person person;

public static void main(String[] args) throws InterruptedException {
    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.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());
    }
}

可能的输出如下:
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]

七、原子更新字段类

如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
AtomicIntegerFieldUpdater 原子更新整形字段
AtomicLongFieldUpdater 原子更新长整型字段
AtomicReferenceFieldUpdater 原子更新引用类型字段

对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
要想原子更新字段,需要两个步骤:

每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
更新类的字段(属性)必须为public volatile
下面的代码演示如何使用原子更新字段类更新字段:

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;


public class AtomicIntegerFieldUpdaterDemo {

    //创建一个原子更新器
    private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(User.class,"old");

    public static void main(String[] args){
        User user = new User("Tom",15);
        //原来的年龄
        System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
        //现在的年龄
        System.out.println(atomicIntegerFieldUpdater.get(user));
    }

    static class User{
        private String name;
        public volatile int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getOld() {
            return old;
        }

        public void setOld(int old) {
            this.old = old;
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值