• 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不是没有阻塞 ,只是阻塞不是语言层面,而是在硬件层面,这样便会更高效。

  • 相关阅读:
    LINUX查看进程开始时间、结束时间、运行时间
    excel字符处理函数
    oracle RMAN参数配置详解
    Linux添加双IP
    免费软电话 — XLite 的安装及配置向导
    Asterisk实现寻呼对讲广播的Page()命令详解
    自动化工具的重要性
    负载均衡之应用请求路由模块的使用(ARR)(七)[使用ARR来实现主机名共享]
    负载均衡之应用请求路由模块的使用(ARR)(二)[安装]
    需求管理随笔
  • 原文地址:https://www.cnblogs.com/zhuyeshen/p/12173064.html
Copyright © 2020-2023  润新知