zoukankan      html  css  js  c++  java
  • 并发包中automic类的原理

      提到同步,我们一般首先想到的是lock,synchronized,但java中有一套更加轻量级的同步方式即atomic类。java的并发原子包里面提供了很多可以进行原子操作的类,比如:

    • AtomicInteger
    • AtomicBoolean
    • AtomicLong
    • AtomicReference

    下面以AtomicInteger类为例:

    package com.javaBase.LineDistance;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 〈一句话功能简述〉;
     * 〈功能详细描述〉
     *
     * @author jxx
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class TestAtomic {
    
        public static AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public static void main(String[] args) throws InterruptedException{
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<1000;i++) {
                        atomicInteger.incrementAndGet();
                    }
                }
            });
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<1000;i++) {
                        atomicInteger.incrementAndGet();
                    }
                }
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("最终结果:" + atomicInteger);
        }
    }

    运行结果:

    最终结果:2000

    由结果可知,atomicInteger类是线程安全的。下面看看incrementAndGet()方法是如何实现的,源码如下:

    /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    
    
    /** 其中getIntVolatile和compareAndSwapInt都是native方法
      * getIntVolatile是获取当前的期望值
      * compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
      */
    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;
        }

      可以看到原子性的实现没有用synchronized,说明是非阻塞同步。最核心的方法是compareAndSwapInt,也就是所谓的CAS操作。CAS操作依赖底层硬件的CAS指令,CAS指令有两个步骤:冲突检测和更新操作,但是这两个步骤合起来成为一个原子性操作。CAS指令需要3个操作数:内存位置(V),旧的预期值(A),新值(B)。CAS指令执行时,首先比较内存位置V处的值和A的值是否相等(冲突检测),如果相等,就用新值B覆盖A(更新操作),否则,就什么也不做。所以,一般循环执行CAS操作,直到成功为止。

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

      Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。 通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。

      CAS也并非完美的,它会导致ABA问题,就是说,当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。

    AtomicStampedReference使用代码:

    package com.wangjun.thread;
    
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    public class ABA {
        
        // 普通的原子类,存在ABA问题
        AtomicInteger a1 = new AtomicInteger(10);
        // 带有时间戳的原子类,不存在ABA问题,第二个参数就是默认时间戳,这里指定为0
        AtomicStampedReference<Integer> a2 = new AtomicStampedReference<Integer>(10, 0);
        
        public static void main(String[] args) {
            ABA a = new ABA();
            a.test();
        }
        
        public void test() {
            new Thread1().start();
            new Thread2().start();
            new Thread3().start();
            new Thread4().start();
        }
        
        class Thread1 extends Thread {
            @Override
            public void run() {
                a1.compareAndSet(10, 11);
                a1.compareAndSet(11, 10);
            }
        }
        class Thread2 extends Thread {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);  // 睡0.2秒,给线程1时间做ABA操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicInteger原子操作:" + a1.compareAndSet(10, 11));
            }
        }
        class Thread3 extends Thread {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);  // 睡0.5秒,保证线程4先执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int stamp = a2.getStamp();
                a2.compareAndSet(10, 11, stamp, stamp + 1);
                stamp = a2.getStamp();
                a2.compareAndSet(11, 10, stamp, stamp + 1);
            }
        }
        class Thread4 extends Thread {
            @Override
            public void run() {
                int stamp = a2.getStamp();
                try {
                    Thread.sleep(1000);  // 睡一秒,给线程3时间做ABA操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicStampedReference原子操作:" + a2.compareAndSet(10, 11, stamp, stamp + 1));
            }
        }
    }

    可以看到使用AtomicStampedReference进行compareAndSet的时候,除了要验证数据,还要验证时间戳。如果数据一样,但是时间戳不一样,那么这个数据其实也被修改过了。

  • 相关阅读:
    hdu 2665 划分树
    概率模型与条件随机场
    shell中各种括号的作用()、(())、[]、[[]]、{}
    今日BBC
    小贝_mysql主从复制作用以及案例
    c++ builder 版CreateAnonymousThread用法
    安卓UI适配限定符
    编译3.10内核 出现错误 “undefined reference to....&quot; 解决方法
    iptables的4表5链(未完)
    已有iptables表的查看
  • 原文地址:https://www.cnblogs.com/jxxblogs/p/12015832.html
Copyright © 2011-2022 走看看