Java多线程/并发16、Atomic原子变量和原子操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/soonfly/article/details/70920276

在Java中,i++这类的操作看起来只有一行,其实java 分成了三步去做
1、获取i值
2、计算i+1;
3、将结果存入i;
因此i++不是原子操作,非线程安全的,多线程访问的时候需要用到synchronized关键字保持线程同步。synchronized是悲观锁,在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,代价就是效率低下。
更加高效的锁就是乐观锁,所谓乐观锁就是不是每次都加锁,而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
接下来要讲的Atomic则通过 CAS(Compare and Swap即比较并交换)的一种乐观锁机制在cpu层面实现的线程安全的操作接口,可以自动实现同步。

本文讲述Atomic系列的类的实现以及使用方法,其中包含:
基本类型:AtomicInteger、AtomicLong、AtomicBoolean;
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;

一、基本类型使用

只需要看懂一个,其余的方法和使用都是大同小异的。
我们来看一下最简单的AtomicInteger有哪些常见的方法以及这些方法的作用。

  • getAndAdd(int) 增加指定的数据,返回变化前的数据
  • getAndDecrement() 减少1,返回减少前的数据
  • getAndIncrement() 增加1,返回增加前的数据
  • getAndSet(int) 设置指定的数据,返回设置前的数据
  • addAndGet(int) 增加指定的数据后返回增加后的数据
  • incrementAndGet() 增加1,返回增加后的值
  • compareAndSet(int, int) 尝试新增后对比,若增加成功则返回true否则返回false
public class AtomicTest {
    public final static AtomicInteger CONCURRENT_INTEGER = new AtomicInteger(1);
    public static void main(String[] args) {
        Thread[] threads=new Thread[10];
        for(int i=0;i<10;i++){
            threads[i]=new Thread(){
                @Override
                public void run() {
                    int num=CONCURRENT_INTEGER.addAndGet(2);
                    System.out.println("线程"+Thread.currentThread().getName()+"获取值后加2,结果为:"+num);
                }
            };
        }
        for(Thread t:threads ){
            t.start();
        }
        for(Thread t:threads ){
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程"+Thread.currentThread().getName()+"最终获取结果为:"+CONCURRENT_INTEGER.get());
    }

}

输出结果:

线程Thread-0获取值后加2,结果为:3
线程Thread-1获取值后加2,结果为:5
线程Thread-3获取值后加2,结果为:7
线程Thread-2获取值后加2,结果为:9
线程Thread-4获取值后加2,结果为:11
线程Thread-6获取值后加2,结果为:13
线程Thread-5获取值后加2,结果为:15
线程Thread-8获取值后加2,结果为:17
线程Thread-9获取值后加2,结果为:21
线程Thread-7获取值后加2,结果为:19
线程main结果为:21

虽然线程处于竞争,顺序错乱,但每条线程输出的结果是正确的。因为当一个线程在操作的时候,会对其它线程进行排斥,不用我们手动去使用synchronized实现互斥操作。AtomicLong和AtomicBoolean类似。

二、数组类型使用

先来看一下AtomicIntegerArray有哪些常见的方法以及这些方法的作用

  • addAndGet(int, int) 执行加法,第一个参数为数组的下标,第二个参数为增加的数量,返回增加后的结果
  • decrementAndGet(int) 参数为数组下标,将数组对应数字减少1,返回减少后的数据
  • incrementAndGet(int) 参数为数组下标,将数组对应数字增加1,返回增加后的数据
  • getAndAdd(int, int) 和addAndGet类似,区别是返回值是变化前的数据
  • getAndDecrement(int) 和decrementAndGet类似,区别是返回变化前的数据
  • getAndIncrement(int) 和incrementAndGet类似,区别是返回变化前的数据
  • getAndSet(int, int) 将对应下标的数字设置为指定值,第二个参数为设置的值,返回是变化前的数据
  • compareAndSet(int, int, int) 对比修改,参数1:数组下标,参数2:原始值,参数3,修改目标值,修改成功返回true否则false
public final static AtomicIntegerArray CONCURRENT_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{35,99});
    public static void main(String[] args) {
        Thread[] threads=new Thread[10];
        for(int i=0;i<10;i++){
            threads[i]=new Thread(){
                @Override
                public void run() {
                    int result0=CONCURRENT_INTEGER_ARRAY.addAndGet(0,5);
                    int result1=CONCURRENT_INTEGER_ARRAY.addAndGet(1,7);
                    System.out.println("线程"+Thread.currentThread().getName()+"为数组元素[0]加5,结果为:"+result0+";为数组元素[1]加7,结果为:"+result1);
                }
            };
        }
        for(Thread t:threads ){
            t.start();
        }
        for(Thread t:threads ){
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("======查看数组结果======");
        for(int i = 0 ; i < CONCURRENT_INTEGER_ARRAY.length() ; i++) {
            System.out.println("数组元素["+i+"]值为:"+CONCURRENT_INTEGER_ARRAY.get(i));
        }

    }

输出结果:

线程Thread-0为数组元素[0]加5,结果为:40;为数组元素[1]加7,结果为:106
线程Thread-2为数组元素[0]加5,结果为:45;为数组元素[1]加7,结果为:113
线程Thread-1为数组元素[0]加5,结果为:50;为数组元素[1]加7,结果为:120
线程Thread-3为数组元素[0]加5,结果为:55;为数组元素[1]加7,结果为:127
线程Thread-4为数组元素[0]加5,结果为:60;为数组元素[1]加7,结果为:134
线程Thread-5为数组元素[0]加5,结果为:65;为数组元素[1]加7,结果为:141
线程Thread-8为数组元素[0]加5,结果为:75;为数组元素[1]加7,结果为:155
线程Thread-6为数组元素[0]加5,结果为:70;为数组元素[1]加7,结果为:148
线程Thread-7为数组元素[0]加5,结果为:80;为数组元素[1]加7,结果为:162
线程Thread-9为数组元素[0]加5,结果为:85;为数组元素[1]加7,结果为:169
======查看数组结果======
数组元素[0]值为:85
数组元素[1]值为:169

三、属性原子修改器AtomicIntegerFieldUpdater

应用于复杂对象的属性操作
方法(说明上和AtomicInteger几乎一致,唯一的区别是第一个参数需要传入对象的引用)
定义修改器时,要注意
1、定义修改器时用AtomicIntegerFieldUpdater泛型类,需要指明类类型<T>
2、newUpdater工厂方法接收两个参数,分别是对象的类型,要修改的类属性名称。该方法返回修改器对象。
全部代码如下:

package JConcurrence.Study;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

class Dummy {
    /* 必须指定为volatile类型,否则属性修改器不能工作 */
    public volatile int number;
}

public class AtomicTest3 {
    /*
     * 1、定义修改器时用AtomicIntegerFieldUpdater泛型类,需要指明类类型<T>。
     * 2、newUpdater工厂方法接收两个参数,分别是对象的类型,要修改的类属性名称。该方法返回修改器对象。
     */
    public final static AtomicIntegerFieldUpdater<Dummy> FIELD_INTEGER_UPDATER = AtomicIntegerFieldUpdater
            .newUpdater(Dummy.class, "number");

    public static void main(String[] args) {
        final Dummy dummy = new Dummy();

        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread() {
                @Override
                public void run() {
                    int num = FIELD_INTEGER_UPDATER.addAndGet(dummy, 2);
                    System.out.println("线程" + Thread.currentThread().getName()
                            + "获取值后加2,结果为:" + num);
                }
            };
        }
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程" + Thread.currentThread().getName()
                + "最终获取dummy.number结果为:" + dummy.number);
    }

}

四、引用类型AtomicReference使用

先看常见的方法以及这些方法的作用

  • boolean compareAndSet(V expect, V update)如果当前值 ==
  • V get() 获取当前值。
  • V getAndSet(V newValue)以原子方式设置为给定值,并返回旧值。
  • void lazySet(V newValue)最终设置为给定值。
  • void set(V newValue)设置为给定值 String toString()返回当前值的字符串表示形式。
  • boolean weakCompareAndSet(V expect, V update)如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

很好理解,AtomicReference的用法和基本类型AtomicInteger的用法很类似。
AtomicReference作用于类,当然也可以作用在int值上。用法不解释了,只是这里要注意一个问题,对于引用类型,compareAndSet函数是如何判断期望值和当前值是否相等的。

先定义一个外部类:

class User {
    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }
    private int id;
    private String username;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public String toString() {
        return "User["+this.id+"]名称:"+this.username;
    }
}

然后在主线程中调用:

public class AtomicTest4 {
    public static void main(String[] args) {
        User userA = new User(1,"叶开");
        User userB = new User(2,"傅红雪");

        /*定义并用userA初始化AtomicReference对象*/
        AtomicReference<User> ATOMIC_REFERENCE = new AtomicReference<User>(userA);

        User user=ATOMIC_REFERENCE.get();
        ATOMIC_REFERENCE.compareAndSet(user, userB);
        System.out.println(ATOMIC_REFERENCE.get());
    }
}

功能很简单:ATOMIC_REFERENCE在初始化时,置入对象userA(叶开)
接下来调用方法compareAndSet()判断ATOMIC_REFERENCE持有对象如果是userA(叶开),就置为userB(傅红雪)

现在我们调整一下程序。在User user=ATOMIC_REFERENCE.get();下面增加一行修改User的名字

user.setUsername("李寻欢");

再次运行,发现还是输出“傅红雪”。AtomicReference认为对象并没有发生变化。
我们尝试重写equals方法再试一下。
为User对象增加equals()方法,只判断username是否相等。
在User类中重载方法equals

/*只比较名字是否一样,不判断ID是否一致*/
    @Override
    public boolean equals(Object obj) {
        if(this==obj){
            return true;
        }
        if(obj instanceof User){
            User tempUser=(User)obj;
            if(this.getUsername()==tempUser.getUsername()){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

再次运行,还是输出“傅红雪”。
这么以来我们可以最终认定AtomicReference的比较机制是比较两个对象的地址,也就是采用“==”比较,而不采用equals。

阅读更多

没有更多推荐了,返回首页