• 从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法和Unsafe部分源码介绍,valueOffset偏移量的理解


    例子

    i++的简单流程

    众所周知,i++分为三步:

    1. 读取i的值

    2. 计算i+1

    3. 将计算出i+1赋给i

    保证i++操作的线程安全

    用锁和volatile

    可以使用来保持操作的原子性变量可见性,用volatile保持值的可见性和操作顺序性

    从一个小例子引发的JAVA内存可见性的简单思考和猜想以及DCL单例模式中的VOLATILE的作用:https://www.cnblogs.com/theRhyme/p/12145461.html

    用java.util.concurrent.atomic包下的原子类

    如果仅仅是计算操作,我们自然就想到了java.util.concurrent.atomic包下的原子类,则不必考虑锁的升级、获取、释放等消耗,也不必考虑锁的粒度、种类、可重入性等;

    由于atomic由于底层是Unsafe对象的CAS操作,缺点也很明显:需要循环时间开销,只能是单个变量CASABA问题(通过AtomicStampedReference解决——增加了stamp类似于version标识)。

    AtomicInteger方法源码

    incrementAndGet方法源码

    public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }

    incrementAndGet,先increment,再get,所以获取的是increment后的值,而unsafe.getAndAddInt先get,所以这里需要"+1";

    valueOffset是什么呢?

      private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }

    AtomicInteger的静态属性valueOffset属性value的偏移量,在类加载的时候,通过AtomicInteger的Field——"value"初始化,后续通过当前的AtomicInteger实例和该valueOffset obtain该实例value属性的值;

    个人对valueOffset的理解

    如果想获取一个对象的属性的值,我们一般通过getter方法获得,而sun.misc.Unsafe却不同,我们可以把一个对象实例想象成一块内存,而这块内存中包含了一些属性,如何获取这块内存中的某个属性呢?那就需要该属性在该内存的偏移量了,每个属性在该对象内存中valueOffset偏移量不同,每个属性的偏移量在该类加载或者之前就已经确定了(则该类的不同实例的同一属性偏移量相等),所以sun.misc.Unsafe可以通过一个对象实例该属性的偏移量用原语获得该对象对应属性的值

    sun.misc.Unsafe#getAndAddInt IDEA反编译后的源码(与JMM相呼应)

    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;
        }

    unsafe.getAndAddInt具体实现是循环不停的compare主存中取到的值var5(this.getIntVolatile)和当前的线程的工作内存中的值(通过对象实例var1和偏移量var2获得),直到两者equal(保证原子性)则更新为新值var5+var4(delta) ,从这里我们也体会到了Java内存模型(线程不能直接操作主存中的值,需要复制一份到自己的工作内存中)

    getIntVolatile(主存)和compareAndSwapInt都是sun.misc.Unsafe的native方法。

    总结

    AtomicInteger通过Unsafe对象保证原子性,而Unsafe对象的getAndAddInt方法通过循环比较主存和线程工作内存中的属性值相等后更新(即CAS)来保证原子性

    AtomicInteger的value属性也被volatile关键字修饰:volatile关键字的作用

    private volatile int value;

    保证i++原子性的几种方式

    下面是一个非常非常简单的小例子,分别是非线程安全的i++,锁同步以及Atomic Class:

    public class Counter {
        @Getter
        private volatile int i = 0;
    
        @Getter
        private volatile AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public void increment(){
            i++;
            // 1. 读取i的值
            // 2. 计算i+1
            // 3. 把i+1的值赋给i
        }
    
        public void incrementSync(){
            synchronized(this) {
                i++;
                // 1. 读取i的值
                // 2. 计算i+1
                // 3. 把i+1的值赋给i
            }
        }
    
        public void incrementAtomic(){
            // 先increment再返回
            atomicInteger.incrementAndGet();
        }
    }

    测试类:

    public class AtomicityTest {
        private Counter counter;
        /**
         * 每个线程打印的次数
         */
        private int count;
    
        @Before
        public void init(){
            counter = new Counter();
            count = 10000;
        }
    
        /**
         * 非线程安全的i++
         */
        @Test
        public void increment() throws InterruptedException {
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.increment();
                }
            });
    
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.increment();
                }
            });
    
            t1.start();
            t2.start();
    
            // 单元测试必须新起的线程要在主线程里join,否则主线程运行完毕,新起的线程还执行完
            t1.join();
            t2.join();
            /*
            ThreeParamsEquals<Enum, Enum, Enum> equals = (Enum p1, Enum p2, Enum p3) -> p1.equals(p2) && p1.equals(p3);
            while (true){
                if (equals.equals(Thread.State.TERMINATED, t1.getState(), t2.getState())) {
                    break;
                }
            }*/
            System.out.println(t1.getState());
            System.out.println(t2.getState());
            // 由于不是原子性操作,两个线程执行总共20000次i++,结果i的值都小于20000
            System.out.println(counter.getI());
        }
    
    
        /**
         * 线程安全的i++
         */
        @Test
        public void incrementSync() throws InterruptedException {
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.incrementSync();
                }
            });
    
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.incrementSync();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println(t1.getState());
            System.out.println(t2.getState());
            // 由于保证了原子性,顺序性,可见性操作,两个线程执行总共20000次i++,结果i的值都等于20000
            System.out.println(counter.getI());
        }
    
        /**
         * 原子类AtomicInteger
         */
        @Test
        public void incrementAtomic() throws InterruptedException {
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.incrementAtomic();
                }
            });
    
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < count; i++) {
                    counter.incrementAtomic();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println(t1.getState());
            System.out.println(t2.getState());
            // 由于保证了原子性,顺序性,可见性操作,两个线程执行总共20000次i++,结果i的值都等于20000
            System.out.println(counter.getAtomicInteger());
        }
    }
  • 相关阅读:
    20189207《网络攻防实践》第一周作业
    事件冒泡
    链接分类
    JS:offsetWidth\offsetleft
    JS alert()、confirm()、prompt()的区别
    this用法
    事件绑定
    clippath
    浅谈正则
    C++大师Lippman:我对中国程序员的忠告(转载)
  • 原文地址:https://www.cnblogs.com/theRhyme/p/12129120.html
Copyright © 2020-2023  润新知