Java原子性操作之——Atomic包的原理分析

Posted 那些年的代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java原子性操作之——Atomic包的原理分析相关的知识,希望对你有一定的参考价值。


Atomic:

    Atomic包是java.util.concurrent下的另一个专门为线程安全设计的java的包,包含多个原子性操作的类。基本特性就是在多线程情况下,当多个线程想要同时操作这些类的某些实例方法时,具有排他性,也就是当某个线程在执行某个方法时,不会被其他线程打断,其他线程会在外部等待,一直等到该方法执行完毕,才由JVM从等待队列中选择另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(只是在硬件级别去阻塞了)。可以对基本数据,数组中的基本数据,对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读写操作。

    我们先看一下传统的锁是怎样保证线程安全的

   

class LockDemo {

    private int a;

    public synchronized void setA(int b) {
        this.a = b;
    }

    public synchronized int getA() {
        return a;
    }

    public synchronized void addA() {
        ++a;
    }

    public synchronized void reduceA() {
        --a;
    }

}
其实这样的synchronized已经能满足我们日常的线程安全需求了,synchronized是基于代码阻塞的机制,也就是当某个线程占用资源时,其他线程是无法进入的,如果这个线程出现问题的时候,出现大量线程阻塞,CPU就会耗费大量资源来处理阻塞在外的这些线程,但是CPU的任务本不该如此,还极可能出现死锁等问题,对于这样的简单操作反而显得有些笨重,所以应该有更合适更高效的方法来处理这样的问题。所以就有了CAS

Compare and swap(CAS)

当前的处理器基本都支持CAS,这是一种基于硬件的处理,每一个CAS操作都包含三个运算符:内存地址V , 一个期望值A和新值B,操作的时候如果这个地址上存放的值等于期望的值A,那么就赋值为B,如果没有,那么就不做任何操作。简而言之,你的值和我的期望值相等,就给你新值,否则不干活了。

我们自己可以简单的模拟一下CAS的实现

class CASDemo {

    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized int cas(int expectValue , int newValue) {

        int oldValue = value;
        if(value == expectValue) {
            value = newValue;
        }
        return oldValue;

    }

}

class CASTest {

    private CASDemo casDemo;

    public int getValue () {
        return casDemo.getValue();
    }

    public int add () {
        int oldValue = casDemo.getValue();
        while(casDemo.cas(oldValue , oldValue + 1) != oldValue) {
            oldValue = casDemo.getValue();
        }

        return oldValue + 1;
    }

}
看下Atomic包



看一下AtomicInteger类是怎么处理自增的处理的,也就是方法getAndIncrement()



这里调用了一个叫做Unsafe类的方法,这个类比较特殊内部大多是native的方法,而且这个类也不允许我们随意使用,当然JDK自己是可以用的,(ps:处于凡是试试的态度,我试了一下调用它的方法,报错如下)

看一下valueOffset是哪来的

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
这里又调用了Unsafe类的一个方法,参数是通过反射获取的AtomicInteger的value属性,也就是它的值

继续进

public native long objectFieldOffset(Field var1);


很可惜,是一个本地方法,查看文档可知,这个方法返回的就是"原来的值"的内存地址 , valueOffset的值(ps:文档是其他某位大神那里借来的)

返回一开始的方法 点进去看

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
参数比较混乱 var2就是valueOffset 继续跟进

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
喔,又是一个本地方法,参数很乱  按我们之前的讲就是:

 var2(valueOffset)  , var4(expectValue) , var5(newValue)

我再借一点注释



这个方法原子的将变量的值更新为var5,如果成功了返回true。这样返回去取反即为false,循环结束,返回var5,取到新值。

当然Unsafe关于CAS的方法都是本地方法,是由C语言实现的,我们这里是看不到具体实现细节的。



    说了半天 到底CAS是怎么保证线程安全的呢,其实在语言层次我们是没有做任何关于同步的操作的,也没有任何锁。Atomic包下这些类将这些操作都交给了CPU和内存,利用CPU多处理的能力,实现硬件的阻塞,再加上volatile变量的特性即可实现基于原子性的操作的线程安全。所以CAS不是没有阻塞 ,只是阻塞不是语言层面,而是在硬件层面,这样便会更高效。

以上是关于Java原子性操作之——Atomic包的原理分析的主要内容,如果未能解决你的问题,请参考以下文章

原子性操作原理分析

JDK源码分析-AtomicInteger

go atomic包分析

八:并发编程之Atomic&Unsafe魔法类详解

深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作

2. 原子性 Atomic