• Atomic类和CAS


    说Atomic类之前,先聊一聊volatile。

    对volatile的第一印象就是可见性。所谓可见性,就是一个线程对共享变量的修改,别的线程能够感知到。

    但是对于原子性,volatile是不能保证的。来看看自增操作的问题:

    volatile int i;
    
    i++;

    i++ 在多线程环境下,是不能保证最终的结果正确的。比如某个时刻,i=5,线程A读取了i的值,说时迟那时快,就在马上要执行++操作时,线程A突然就被切换走了;然后线程B也读取i的值,进行了++操作。这时i的值是6,即使线程A的工作内存中的缓存已经失效,线程A已经读取了i的值为5,不会再去读取,所以++操作后,i的值还是6。

    关于volatile的底层实现,有好多文章分析的很透彻,这里不再赘述。

    那么除了使用synchronized,还有没有其它的方式来解决上述问题呢?天空一声巨响,Atomic类闪亮登场!!!

    先看看AtomicInteger类,其它类型包装成的Atomic类,请读者自习。

    AtomicInteger i = new AtomicInteger(0);
    
    i.incrementAndGet();

    用法相当简单,incrementAndGet 方法能实现原子性的自增操作。如何实现,看源码。

        /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }

    Unsafe类:

        /**
         * Atomically adds the given value to the current value of a field
         * or array element within the given object <code>o</code>
         * at the given <code>offset</code>.
         *
         * @param o object/array to update the field/element in
         * @param offset field/element offset
         * @param delta the value to add
         * @return the previous value
         * @since 1.8
         */
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }
    /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapInt(Object o, long offset,
                                                      int expected,
                                                      int x);

    可以看到原子性的实现没有用synchronized,说明是非阻塞同步。最核心的方法是compareAndSwapInt,也就是所谓的CAS操作。

    CAS操作依赖底层硬件的CAS指令,CAS指令有两个步骤:冲突检测和更新操作,但是这两个步骤合起来成为一个原子性操作。

    CAS指令需要3个操作数:内存位置(V),旧的预期值(A),新值(B)。CAS指令执行时,首先比较内存位置V处的值和A的值是否相等(冲突检测),如果相等,就用新值B覆盖A(更新操作),否则,就什么也不做。所以,一般循环执行CAS操作,直到成功为止。

    Unsafe类里面的compareAndSwapXXX 方法最后都会变成与硬件相关的CAS指令。从Unsafe这个类名就可以看出,作者不希望我们随便使用,因为是不安全的。为什么不安全呢,因为这个类可以直接操作内存;还有其他的一些底层操作,比如上篇文章提到的将线程挂起,就是调用了Unsafe类的park方法(感兴趣的,出门左拐)。

    了解了CAS和Unsafe类,接着再说AtomicInteger类:

        // setup to use Unsafe.compareAndSwapInt for updates
        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); }
        }
    
        private volatile int value;

    value就是我们需要操作的真正int值,unsafe就是Unsafe类的单例,valueOffset在static语句块里面,被设置成了value变量在AtomicInteger类的实例对象里面的偏移量(可以看成内存地址)。这里对对象的内存布局如有疑问,同样出门一路左拐,找到那篇将Oop和Klass的文章。

    也就是说,Atomic类通过循环进行CAS操作,直到成功,来实现非阻塞同步,进而变成原子操作。

    在java.util.concurrent.atomic包下还有一些看上去比较奇怪的类,XXXFieldUpdater类,这玩意儿是用来干什么的呢?

    一句话概括,就是亡羊补牢。比如说,你先自己写了一个类,定义了一个基础类型的变量。后来涉及到多线程,那么原来对该变量的一些操作就变得不安全。如果,你立马想到的是手动修改代码,那就太low了,破坏了设计模式里面比较重要的开闭原则,而且很多情况下,你是接触不到别人的源代码的。这个时候,XXXFieldUpdater类就派上用场了。这个时候,猜也能猜到,这个类肯定是通过反射的方式实现这个功能的。另外,有一点原来定义的继承类型变量,必须是volatile的。

    直接上代码,看效果:

    public class Test{
        public volatile int a = 100;
        
        // 多线程下不安全
        public void incr() {
            a++;
        }  
    }
    public class SafeTest{
        private static AtomicIntegerFieldUpdater<Test> update = AtomicIntegerFieldUpdater.newUpdater(Test.class, "a");
        private static Test test = new Test();
    
        // 多线程下安全
        public void incr() {
           update.incrementAndGet(test);
        }
    }
    update.incrementAndGet(test)无非也是调用了Unsafe类的CAS操作,核心方法是AtomicIntegerFieldUpdater.newUpdater(Test.class, "a"),看看源码:
        @CallerSensitive
        public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                                  String fieldName) {
            return new AtomicIntegerFieldUpdaterImpl<U>
                (tclass, fieldName, Reflection.getCallerClass());
        }
    
    
    
        AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                          final String fieldName,
                                          final Class<?> caller) {
                // 反射逻辑
                final Field field;
                final int modifiers;
                try {
                    field = AccessController.doPrivileged(
                        new PrivilegedExceptionAction<Field>() {
                            public Field run() throws NoSuchFieldException {
                                return tclass.getDeclaredField(fieldName);
                            }
                        });
                    modifiers = field.getModifiers();
                    sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                        caller, tclass, null, modifiers);
                    ClassLoader cl = tclass.getClassLoader();
                    ClassLoader ccl = caller.getClassLoader();
                    if ((ccl != null) && (ccl != cl) &&
                        ((cl == null) || !isAncestor(cl, ccl))) {
                      sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                    }
                } catch (PrivilegedActionException pae) {
                    throw new RuntimeException(pae.getException());
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
    
                Class<?> fieldt = field.getType();
                if (fieldt != int.class)
                    throw new IllegalArgumentException("Must be integer type");
                // 必须是volatile
                if (!Modifier.isVolatile(modifiers))
                    throw new IllegalArgumentException("Must be volatile type");
    
                this.cclass = (Modifier.isProtected(modifiers) &&
                               caller != tclass) ? caller : null;
                this.tclass = tclass;
                // 得到变量的偏移量,相当于内存地址
                offset = unsafe.objectFieldOffset(field);
            }

    总的来说,原子性的实现是依赖于Unsafe类的CAS操作,直接修改内存里的值,既危险又刺激。我们甚至可以用Unsafe类来直接分配内存,要不试一试!!!

    Unsafe类是单例,可以通过它的getUnsafe方法获取这个单例。

        @CallerSensitive
        public static Unsafe getUnsafe() {
            Class<?> caller = Reflection.getCallerClass();
            // 调用方的类的类加载器必须是启动类加载器
            if (!VM.isSystemDomainLoader(caller.getClassLoader()))
                throw new SecurityException("Unsafe");
            return theUnsafe;
        }

    这个方法对调用方有限制,就是说你随随便便定义的类,调用这个方法是会报错的。但是Unsafe类既然已经被加载,我们可以通过反射的方式去获取里面的单例对象。

    package test;
    
    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    import sun.reflect.Reflection;
    
    class User {
        private String name = "";
        private int age = 0;
    
        public User() {
            this.name = "test";
            this.age = 25;
        }
        
        @Override
        public String toString() {
            return name + ": " + age;
        }
    }
    
    
    public class Test {
        public static void main(String[] args) throws NoSuchFieldException,
                SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
            // 通过反射得到theUnsafe对应的Field对象
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            // 设置该Field为可访问
            field.setAccessible(true);
            // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
            Unsafe unsafe = (Unsafe) field.get(null);
    
            // 直接分配相应大小的内存,不执行构造方法
            User user = (User) unsafe.allocateInstance(User.class);
            System.out.println(user);
            
            User userFromNormal = new User();
            System.out.println(userFromNormal);
    
        }
    }

    这个例子演示了Unsafe直接为某个类的实例分配内存,注意是只分配内存,没有顺便调用构造方法。

    运行结果:

    null: 0
    test: 25

    当然,Unsafe类里面还有好多的底层操作,宝藏后面慢慢挖掘。就到这里吧!!!

     
  • 相关阅读:
    JS轮播图
    jquery 60秒倒计时
    jQuery 显示加载更多
    jQuery 显示加载更多
    this指针在不同情况下的指代
    web-app1--移动端等比例代码
    无障碍阅读
    javascript+dom 做javascript图片库
    初探html5---Video + DOM(视频播放)
    14个有效提高网站Banner点击率的设计技巧分享
  • 原文地址:https://www.cnblogs.com/cz123/p/7466166.html
Copyright © 2020-2023  润新知