zoukankan      html  css  js  c++  java
  • 关于Atomic

    一.Atomic是什么

    所谓 Atomic,翻译过来就是原子。原子被认为是操作中最小的单位,一段代码如果是原子的,则表示这段代码在执行过程中,要么执行成功,要么执行失败。

    原子操作一般都是底层通过 CPU 的指令来实现。而 atomic 包下的这些类,则可以让我们在多线程环境下,通过一种无锁的原子操作来实现线程安全。

    atomic 包下的类基本上都是借助 Unsafe 类,通过 CAS 操作来封装实现的。Unsafe 这个类不属于 Java 标准,或者说这个类是 Java 预留的一个后门类,

    JDK 中,有关提升性能的 concurrent 或者 NIO 等操作,大部分都是借助于这个类来封装操作的。

    Java 是种编译型语言,不像 C 语言能支持操作内存,正常情况下都是由 JVM 进行内存的创建回收等操作,但这个类提供了一些直接操作内存相关的底层操作,

    使得我们也可以手动操作内存,但从类的名字就可以看出,这个类不是安全的,官方也是不建议我们使用的。

    二.Atomic能做什么

    在多线程或者并发环境中,常常会遇到这种情况 int i=0; i++ 。稍有经验的同学都知道这种写法是线程不安全的。

    为了达到线程安全的目的,通常会用synchronized来修饰对应的代码块。现在我们有了新的方法,就是使用J.U.C包下的atomic类。

    三.Atomic原理

    一句话来说,atomic类是通过自旋CAS操作volatile变量实现的。

    CAS是compare and swap的缩写,即比较后(比较内存中的旧值与预期值)交换(将旧值替换成预期值)。它是sun.misc包下Unsafe类提供的功能,需要底层硬件指令集的支撑。

    使用volatile变量是为了多个线程间变量的值能及时同步。

    按理来说,使用synchroized已经能满足功能需求了。为什么还会有这个类呢?那肯定是性能的问题了。

    在JDK1.6之前,synchroized是重量级锁,即操作被锁的变量前就对对象加锁,不管此对象会不会产生资源竞争。这属于悲观锁的一种实现方式。

    而CAS会比较内存中对象和当前对象的值是否相同,相同的话才会更新内存中的值,不同的话便会返回失败。这是乐观锁的一中实现方式。这种方式就避免了直接使用内核状态的重量级锁。

    但是在JDK1.6以后,synchronized进行了优化,引入了偏向锁,轻量级锁,其中也采用了CAS这种思想,效率有了很大的提升。

    四.Atomic使用

    Atomic包里一共有12个类,主要提供四种原子更新方式:

    1、原子方式更新基本类型

    以下三个类是以原子方式更新基本类型

    (1)AtomicBoolean:原子更新布尔类型。

    (2)AtomicInteger:原子更新整型。

    (3)AtomicLong:原子更新长整型。

    以AtomicInteger为例:

    package concurrency;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerTest {
    
        static AtomicInteger ai = new AtomicInteger(1);
    
        public static void main(String[] args) {
            //相当于i++,返回的是旧值,看方法名就知道,先获取再自增
            System.out.println(ai.getAndIncrement());
            System.out.println(ai.get());
            //先自增,再获取
            System.out.println(ai.incrementAndGet());
            System.out.println(ai.get());
            //增加一个指定值,先add,再get
            System.out.println(ai.addAndGet(5));
            System.out.println(ai.get());
            //增加一个指定值,先get,再set
            System.out.println(ai.getAndSet(5));
            System.out.println(ai.get());
        }
    
    }

    注意:

    Atomic包提供了三种基本类型的原子更新,剩余的Java的基本类型还有char,float和double等,其更新方式可以参考AtomicBoolean的思路来现,

    AtomicBoolean是把boolean转成整型再调用compareAndSwapInt进行CAS来实现的,类似的short和byte也可以转成整形,float和double可以利用Float.floatToIntBits,

    Double.doubleToLongBits转成整形和长整形进行相应处理;

    2、原子方式更新数组

    以下三个类是以原子方式更新数组,

    (1)AtomicIntegerArray:原子更新整型数组里的元素。

    (2)AtomicLongArray:原子更新长整型数组里的元素。

    (3)AtomicReferenceArray:原子更新引用类型数组里的元素。

    以AtomicIntegerArray为例,其方法与AtomicInteger很像,多了个数组下标索引;

    package concurrency;
    
    import java.util.concurrent.atomic.AtomicIntegerArray;
    
    public class AtomicIntegerArrayTest {
    
        static int[] valueArr = new int[] { 1, 2 };
    
        //AtomicIntegerArray内部会拷贝一份数组
        static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr);
    
        public static void main(String[] args) {
            ai.getAndSet(0, 3);
            //不会修改原始数组value
            System.out.println(ai.get(0));
            System.out.println(valueArr[0]);
        }
    
    }

     

    3、原子方式更新引用

    以下三个类是以原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量;

    (1)AtomicReference:原子更新引用类型。

    (2)AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

    (3)AtomicMarkableReference:原子更新带有标记位的引用类型。

    以AtomicReference为例:

    package concurrency;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    public class AtomicReferenceTest {
    
        public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
    
        public static void main(String[] args) {
            User user = new User("conan", 15);
            atomicUserRef.set(user);
            User updateUser = new User("Shinichi", 17);
            atomicUserRef.compareAndSet(user, updateUser);
            System.out.println(atomicUserRef.get().getName());
            System.out.println(atomicUserRef.get().getOld());
        }
    
        static class User {
            private String name;
            private int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }

     

    4、原子方式更新字段

    以下三个类是以原子方式更新字段:

    (1)AtomicIntegerFieldUpdater:原子更新整型字段的更新器。

    (2)AtomicLongFieldUpdater:原子更新长整型字段的更新器。

    (3)AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。

    以AtomicIntegerFieldUpdater为例:

    package concurrency;
    
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    
    public class AtomicIntegerFieldUpdaterTest {
    
        private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
                .newUpdater(User.class, "old");
    
        public static void main(String[] args) {
            User conan = new User("conan", 10);
            System.out.println(a.getAndIncrement(conan));
            System.out.println(a.get(conan));
        }
    
        public static class User {
            private String name;
            //注意需要用volatile修饰
            public volatile int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }

    5、Atomic类的缺点

    (1)ABA问题:

    对于一个旧的变量值A,线程2将A的值改成B又改成可A,此时线程1通过CAS看到A并没有变化,但实际A已经发生了变化,这就是ABA问题。

    解决这个问题的方法很简单,记录一下变量的版本就可以了,在变量的值发生变化时对应的版本也做出相应的变化,然后CAS操作时比较一下版本就知道变量有没有发生变化。

    atomic包下AtomicStampedReference类实现了这种思路。Mysql中Innodb的多版本并发锁也是这个原理。

    (2)自旋问题:

    atomic类会多次尝试CAS操作直至成功或失败,这个过程叫做自旋。通过自旋的过程我们可以看出自旋操作不会将线程挂起,从而避免了内核线程切换,

    但是自旋的过程也可以看做CPU死循环,会一直占用CPU资源。这种情形在单CPU的机器上是不能容忍的,因此自旋一般都会有个次数限制,即超过这个次数后线程就会放弃时间片,等待下次机会。

    因此自旋操作在资源竞争不激烈的情况下确实能提高效率,但是在资源竞争特别激烈的场景中,CAS操作会的失败率就会大大提高,这时使用中重量级锁的效率可能会更高。

    当前,也可以使用LongAdder类来替换,它则采用了分段锁的思想来解决并发竞争的问题。

    6、总结

    对于jdk1.8的并发包来说,底层基本上就是通过Usafe和CAS机制来实现的。有好处也肯定有一个坏处。

    从好的方面来讲,就是上面AtomicInteger类可以保持其原子性。

    但是从坏的方面来看,Usafe因为直接操作的底层地址,肯定不是那么安全,而且CAS机制也伴随着大量的问题,比如说有名的ABA问题等等。

    JDK8又引入了下面几个类:DoubleAdderLongAdderDoubleAccumulatorLongAccumulator,这些类是对AtomicLong这些类的改进与增强,这些类都继承自Striped64这个类。

  • 相关阅读:
    移动文件夹时,千万别直接移动系统文件夹,应该直接复制或者移动这个系统文件夹内的内容
    UIView的intrinsicContentSize方法,在按钮中重写
    Reveal v4(8796) 使用
    AppCode 2016.3 注册码
    BSD process name correspondlng to current thread: knernel_task Mac OS version Not yet set
    iOS 适配HTTPS
    [Graphics] UIColor created with component values far outside the expected range, Set a breakpoint on UIColorBreakForOutOfRangeColorComponents to debug. This message will only be logged once.
    SB中设置UITextField 无边框,真机上输入汉字聚焦时,文字 下沉
    TTTAttributedLabel 富文本小记
    iOS 应用数据存储的常用方式
  • 原文地址:https://www.cnblogs.com/ZJOE80/p/12911502.html
Copyright © 2011-2022 走看看