JAVA多线程并行计算乐观锁之Atomic系列详解

  1. 从多线程并行计算乐观锁 和 悲观锁 来讲,JAVA中的 lock、synchronized 属于悲观锁,即是在操作某数据的时候总是会认为多线程之间会相互干扰,属于阻塞式的加锁;Atomic系列则属于乐观锁系列,即当操作某一段数据的时候,线程之间是不会相互影响,采用非阻塞的模式,直到更新数据的时候才会进行版本的判断是否值已经进行了修改。

  2. Atomic在JAVA中的家族如下:
    a、基本类:AtomicInteger、AtomicLong、AtomicBoolean;
    b、引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;
    c、数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
    d、属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

  3. 基本类型以AtomicInteger为代表进行讲解。
    首先我们来看看AtomicInteger的关键性代码如下:

    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
    }

通过以上代码可以发现,内部定义了一个 volatile 修饰的 value,volatile修饰的变量我们知道保证了内存的的可见性,也就是volatile定义的值只会存在于内存,而抛弃了多核或者多CPU的多级缓存。

volatile保证了内存可见性,而sun.misc.Unsafe 则保证了原子性操作,因为Unsafe中引用了大量的native定义的本地代码,也就是C代码,并且采用CAS(compare and swap) 模式,保证了每次值的更新只会有一个线程会更新成功,其他更新失败的线程会进行循环,直到成功为止。

通过以上2点,这里找到了为什么Atomic除了能保证 volatile所具备的内存可见性功能外,还具备了原子性操作的原理。

请见unsafe的调用方法:

 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);//本方法采用了关键性的CAS操作。

除了AtomicStampedRerence 和 AtomicMarkableReference,其他类都会存在ABA问题,ABA问题请详见上一篇博文的乐观锁 和 悲观锁的解释,AtomicStampedRerence 和 AtomicMarkableReference 分别通过版本号 和 标记来解决ABA问题,以下以AtomicStampedRerence 为例。

AtomicStampedRerence 源码解析:

    private static class Pair {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static  Pair of(T reference, int stamp) {
            return new Pair(reference, stamp);
        }
    }

    private volatile Pair pair;


    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

通过以上源码可以得到:
a、AtomicStampedRerence 是通过对象引用 reference 和 版本号 stamp共同来控制CAS操作。
b、在compareAndSet 可以发现,只有新值 和 期望值都符合要求才算成功。

  • ABA测试:
 public static void main(String []args) {
        for(int i = 0 ; i < 100 ; i++) {
            final int num = i;
            new Thread() {
                public void run() {
                    try {

                        Thread.sleep(Math.abs((int)(Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) {
                        System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");


                    }
                }
            }.start();
        }
        new Thread() {
            public void run() {
                System.out.println("最初值:" +  ATOMIC_REFERENCE.get());
                while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc"));
                System.out.println("结束值:" +  ATOMIC_REFERENCE.get());
                System.out.println("已经改为原始值!");
            }
        }.start();
    }

a、根据Atomic的原理可知,for(int i = 0 ; i < 100 ; i++) 中虽然创建了 100个线程,但是有且只有一个线程对 ATOMIC_REFERENCE.compareAndSet(“abc” , “abc2”) 进行修改,即是 abc 被修改成 abc2只有一个线程能修改成功,而当值被修改成 abc2之后,其他线程再次进来发现堆栈中不是 abc而是 abc2 则 总是失败。
b、另外开一个线程将 abc2修改为 abc之后,for循环中的后续线程当发现堆栈中是abc,那么就回将其重新修改为abc2,如下图运行结果所示。
JAVA多线程并行计算乐观锁之Atomic系列详解_第1张图片

AtomicStampedRerence 解决 ABA问题:

 public static void main(String []args) {
        for(int i = 0 ; i < 100 ; i++) {
            final int num = i;
            int stamped = ATOMIC_REFERENCE.getStamp();
            new Thread() {
                public void run() {
                    try {

                        Thread.sleep(Math.abs((int)(Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2", stamped, stamped + 1)) {
                        System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");


                    }
                }
            }.start();
        }
        int stamped1 = ATOMIC_REFERENCE.getStamp();
        new Thread() {
            public void run() {
                System.out.println("最初值:" +  ATOMIC_REFERENCE.getReference());
                while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc", stamped1, stamped1 + 1));
                System.out.println("结束值:" +  ATOMIC_REFERENCE.getReference());
                System.out.println("已经改为原始值!");
            }
        }.start();
    }

直接贴出以上采用 AtomicStampedRerence 的运行结果:
JAVA多线程并行计算乐观锁之Atomic系列详解_第2张图片

从运行结果可以发现,解决掉了ABA问题,所有线程中,有且只有一个线程修改了值。

参见:https://blog.csdn.net/xh16319/article/details/17056767

你可能感兴趣的:(JAVA内核,JAVA并行计算)