• 深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作(循环CAS)


    java中,可能有一些场景,操作非常简单,但是容易存在并发问题,比如i++,

    此时,如果依赖锁机制,可能带来性能损耗等问题,

    于是,如何更加简单的实现原子性操作,就成为java中需要面对的一个问题。

    在backport-util-concurrent没有被引入java1.5并成为JUC之前,

    这些原子类和原子操作方法,都是使用synchronized实现的。

    不过JUC出现之后,这些原子操作 基于JNI提供了新的实现,

    比如AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference,AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray;

    这些操作中提供一些原子化操作,比如incrementAndGet(相当于i++),compareAndSet(安全赋值)等,直接读源代码也很容易懂。

    以AtomicInteger为例,看看它是怎么做到的:

    如果是读取值,很简单,将value声明为volatile的,就可以保证在没有锁的情况下,数据是线程可见的:

    1     private volatile int value;

         public final int get() { 2 return value; 3 }

    那么,涉及到值变更的操作呢?以AtomicInteger实现:++i为例:

    复制代码
    1     public final int incrementAndGet() {
    2      for (;;) {
    3        int current = get();
    4        int next = current + 1;  
    5        if (compareAndSet(current, next))  
    6          return next;  
    7        }
    8     } 
    复制代码

    在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

    而这里的comparAndSet(current,next),就是前面介绍CAS的时候所说的依赖JNI实现的乐观锁做法:

        public final boolean compareAndSet(int expect, int update) {  
           return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    数组原子化

    注意,Java中Atomic*Array,并不是对整个数组对象实现原子化(也没有必要这样做),而是对数组中的某个元素实现原子化。

    例如,对于一个整型原子数组,其中的原子方法,都是对每个元素的:

    复制代码
    1 public final int getAndDecrement(int i) {
    2       while (true) {
    3        int current = get(i);
    4        int next = current - 1;
    5        if (compareAndSet(i, current, next))
    6             return current;
    7       }
    8 } 
    复制代码

    引用的原子化操作

    引用的操作本身不就是原子的吗?

    一个对象的引用,从A切换到B,本身也不会出现 非原子操作啊?这种想法本身没有什么问题,

    但是考虑下嘛的场景:对象a,当前执行引用a1,

    线程X期望将a的引用设置为a2,也就是a=a2,

    线程Y期望将a的引用设置为a3,也就是a=a3。

    X要求,a必须从a1变为a2,也就是说compareAndSet(expect=a1,setValue=a2);

    Y要求,a必须从a1变为a3,也就是说compareAndSet(expect=a1,setValue=a3)。

    如果严格遵循要求,应该出现X把a的引用设置为a2后,Y线程操作失败的情况,也就是说:

    X:a==a1--> a=a2;

    Y:a!=a1 --> Exception;

    如果没有原子化,那么Y会直接将a赋值为a3,从而导致出现脏数据。

    这就是原子引用AtomicReference存在的原因。

    复制代码
    1      public final V getAndSet(V newValue) {
    2            while (true) {
    3                V x = get();
    4                if (compareAndSet(x, newValue))
    5                    return x;
    6            }
    7        }
    复制代码

    注意,AtomicReference要求引用也是volatile的。

    Updater原子化

    其它几个Atomic类,都是对被volatile修饰的基本数据类型的自身数据进行原子化操作,

    但是如果一个被volatile修饰的变量本身已经存在在类中,那要如何提供原子化操作呢?

    比如,一个Person,其中有个属性为age,private volatile int age,

    如何对age提供原子化操作呢?

    1 private AtomicIntegerFieldUpdater<Person> updater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");  
    2 updater.getAndIncrement(5);//加5岁
    3 updater.compareAndSet(person, 30, 35)//如果一个人的年龄是30,设置为35。

    Java中的Atomic包使用指南

    参考: http://ifeve.com/java-atomic/

  • 相关阅读:
    字符编码笔记:ASCII,Unicode和UTF8(转)
    如何让vs2005的网站编译成一个DLL
    全力奔跑
    工作心得之再谈“表现”
    IT外企那点事[转载]
    直面奋斗
    C#图片水印代码整理
    常用js代码
    一个很有趣的程序员等级考试题求循环小数
    String.Format(字符串输出格式)
  • 原文地址:https://www.cnblogs.com/my376908915/p/6758415.html
Copyright © 2020-2023  润新知