zoukankan      html  css  js  c++  java
  • CAS在Java类中的应用

    CAS

    这个指令全称 compare and swap 即比较替换指令,在现代处理器新加入的指令。
    指导思想:基于乐观锁机制。比较一个变量在内存值中的值和变量的当前值(旧值)。如果相等,则认为该变量没有发生改变,使用新值替代旧值;否则认为替换失败。

    Unsafe

    在java程序的多线程环境中,如果一个变量被多个线程访问,要保证线程安全,除了 volatile、锁、final、static这些手段外,可以借助java提供的 sun.misc.Unsafe类
    这个类两个特点:
    1.平台相关的(java语言平台无关),所有方法为native类型,c语言写的。提供了很多native类型方法
    更详细点的源码可以看-->OpenJDK中的Unsafe
    2.我们可以以不安全的方式使用这个类,在下面的代码中getUnsafe()给出了推荐的使用方式。使用这个类时也可以借鉴一些JDK中自带的类,比如并发包中的原子类、Random、LockSupport.park()、ConcurrentHashMap等。

    下面贴出我对Unsafe类源码的翻译分析

    package sun.misc;
    
    import java.security.*;
    import java.lang.reflect.*;
    
    import sun.reflect.CallerSensitive;
    import sun.reflect.Reflection;
    
    
    /**
     * 一个执行底层非安全的方法集合。虽然这个类及所有方法是公共的,
     * 使用这个类是受限制的,只有被信任的代码才能获得这个类的实例。
     * @author John R. Rose
     * @查阅 方法getUnsafe()
     */
    
    public final class Unsafe {
    
        private static native void registerNatives();
        static {
            registerNatives();
            sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
        }
    	// 私有构造方法,用于单例
        private Unsafe() {}
    	// 私有静态常量属性
        private static final Unsafe theUnsafe = new Unsafe();
    
        /**
         * 这个类的多数方法是底层的,对应于一些硬件指令(在特定的机器)。
    	 * 编译器会积极优化这个方法。
    	 * 对于使用不安全的操作,一个比较推荐的语法:
         * class MyTrustedClass {
         *   private static final Unsafe unsafe = Unsafe.getUnsafe();
         *   ...
         *   private long myCountAddress = ...;
         *   public int getCount() { return unsafe.getByte(myCountAddress); }
         * }
         */
        @CallerSensitive
        public static Unsafe getUnsafe() {
            Class<?> caller = Reflection.getCallerClass();
            if (!VM.isSystemDomainLoader(caller.getClassLoader()))
                throw new SecurityException("Unsafe");
            return theUnsafe;
        }
    
        /* 
    		这个只作用在Java堆的对象属性上。
    		不作用在数组元素上。*/
        public native int getInt(Object o, long offset);
    
        /**
         * 将值存入Java变量。
    	 * 前两个参数和getInt()方法一样,给定的值x被存入变量。
         * 变量必须和方法的参数x同样的类型。
         * @param o 参数o是java堆对象,即任何变量或null所寄存的地方。
         * @param offset 表示变量寄存在Java堆的哪个位置,可以用一个内存地址定位变量位置。
    				注意:此处用long类型表示内存地址,正是因为long 8 个字节,支持64位处理器。
    				如果用int表示内存地址,那只能支持32处理器。
         * @param x 被存入Java变量中的值
         * @throws RuntimeException No defined exceptions are thrown, not even
         *         {@link NullPointerException}
         */
        public native void putInt(Object o, long offset, int x);
    
    
        /**
    	 * 这个方法,像所有其他32位偏移,在之前的发行版本1.4中是native方法,这里为了向后兼容1.4
         * @deprecated As of 1.4.1, cast the 32-bit offset argument to a long.
         * See {@link #staticFieldOffset}.
         */
        @Deprecated
        public int getInt(Object o, int offset) {
            return getInt(o, (long)offset);
        }
    
        /**
         * @deprecated As of 1.4.1, cast the 32-bit offset argument to a long.
         * See {@link #staticFieldOffset}.
         */
        @Deprecated
        public void putInt(Object o, int offset, int x) {
            putInt(o, (long)offset, x);
        }
    
    
        /**
         * 从内存地址获取原生的指针,如果地址是0,或者没有指向一个内存地址块,结果是未知的。
         * 如果原生指针小于64位宽度,它会被作为无符号数扩展到64位。
    	 * 这个指针可以被给定的字节偏定位。从目标地址读到的字节数量可能由地址宽度决定。
         * @see #allocateMemory
         */
        public native long getAddress(long address);
    
        /**
         * 存储一个原生指针到给定的内存地址。如果地址是0或者超过范围,结果将是未知的。
         * 写入到目标地址的实际字节数量是用地址宽度决定的。
         * @see #getAddress(long)
         */
        public native void putAddress(long address, long x);
    
        /// wrappers for malloc分配内存, realloc扩大内存, free释放内存:
    		
        /**
    	 *	分配一个指定字节大小的原生内存块
    	 *	内存内容是未初始化的;没有用的数据
    	 *	结果原生指针将不会是0,将会指派一个值。
         * 通过freeMemory方法处理掉(dispose)内容,或通过reallocateMemory方法重新调整大小
         * @throws IllegalArgumentException 如果大小是负数或原生size_t太大,将会抛异常。
         *
         * @throws OutOfMemoryError if the allocation is refused by the system 
    	 *							如果系统拒绝分配内存,则抛内存溢出错误
         * @see #getByte(long)
         * @see #putByte(long, byte)
         */
        public native long allocateMemory(long bytes);
    
        /**
    	 * 将指定内存块的所有字节设置为固定值(通常为0)
    	 * 这个方法通过两个参数决定一个块的基本地址,所以它提供了一个双寄存器寻址模式addressing mode,
    	 * 正如getInt(Object,long)中所讨论的。当对象引用为null时,offset提供了(supply)一个绝对地址。
         * 
         * 存储内容是连续的单元,大小由地址和参数长度决定的,如果地址有效且能被8取模,存储内容用long单元;
    	 * 如果地址有效且能被4或2取模,就用int 或short取代。
         * @since 1.7
         */
        public native void setMemory(Object o, long offset, long bytes, byte value);
        /**
         * Sets all bytes in a given block of memory to a fixed value (usually zero).
    	 * 将指定内存块的所有字节设置为固定值(通常为0)
    	 * This provides a single-register addressing mode,as discussed in #getInt(Object,long).
    	 * 这个方法提供了单寄存器寻址模式
         * 相同于 Equivalent to setMemory(null, address, bytes, value).
         */
        public void setMemory(long address, long bytes, byte value) {
            setMemory(null, address, bytes, value);
        }
    
        /**
         * 复制内存块,这个方法提供了双寄存器寻址模式,当对象引用为null时,offset表示绝对地址。
         * 这种复制在由大小确定的内存连续单元进行。
         * @since 1.7
         */
        public native void copyMemory(Object srcBase, long srcOffset,
                                      Object destBase, long destOffset,
                                      long bytes);
        /**
         * 复制内存块,这个方法提供了单寄存器寻址模式
         * Equivalent to <code>copyMemory(null, srcAddress, null, destAddress, bytes)</code>.
         */
        public void copyMemory(long srcAddress, long destAddress, long bytes) {
            copyMemory(null, srcAddress, null, destAddress, bytes);
        }
    
        /**
         * 将从#allocateMemory,#reallocateMemory获得obtained的原生native内存块清除dispose掉。
    	 * address为null时,不做任何处理。
         * @see #allocateMemory
         */
        public native void freeMemory(long address);
    
        /// random queries
    
        /**
         * This constant differs from all results that will ever be returned from
         * {@link #staticFieldOffset}, {@link #objectFieldOffset},
         * or {@link #arrayBaseOffset}.
         */
        public static final int INVALID_FIELD_OFFSET   = -1;
    
        /**
         * Returns the base address for accessing some static field in the given class. 
    	 * 为访问静态变量而返回指定类的基本地址
    	 */
        @Deprecated
        public Object staticFieldBase(Class<?> c) {
            Field[] fields = c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                if (Modifier.isStatic(fields[i].getModifiers())) {
                    return staticFieldBase(fields[i]);
                }
            }
            return null;
        }
    
        /**
         * Report the location of a given static field, in conjunction with {@link #staticFieldBase}.
    	 * 报告一个指定静态属性的位置,和#staticFieldBase配合执行。
         * <p>Do not expect to perform any sort of arithmetic on this offset;
    	 * 不要指望在这个偏移量上执行任何类型的运算。
         * it is just a cookie which is passed to the unsafe heap memory accessors.
         * 它只是一个cookie被传递到不安全的堆内存访问 
         * 任何给定字段总是具有相同的偏移量,同一类的两个不同字段不会有相同的偏移量。
         *
         * <p>As of 1.4.1, offsets for fields are represented as long values,
    	 * 虽然SUN JVM不使用最有意义的32位
         * However, JVM implementations which store static fields at absolute
         * addresses can use long offsets and null base pointers to express the field locations in a form usable by {@link #getInt(Object,long)}.
         * 然而,将静态属性存储在绝对地址的JVM版本可以使用长类型偏移和空的基本指针来表示变量位置
    	 * Therefore, code which will be ported to such JVMs on 64-bit platforms must preserve all bits of static field offsets.
    	 * 所以,代码将被移植到64位平台JVM在上必须保留所有位静态字段偏移量 
    	 * @see #getInt(Object, long)
         */
        public native long staticFieldOffset(Field f);
    
        /**
    	 * 报告一个指定静态属性的位置,和#staticFieldBase配合执行。
    	 * 不要指望在这个偏移量上执行任何类型的运算。
         * 它只是一个cookie被传递到不安全的堆内存访问 
         * 任何给定字段总是具有相同的偏移量,同一类的两个不同字段不会有相同的偏移量。 
    	 * JDK1.4.1,字段的偏移值是long类型的值
    	 * 虽然SUN JVM不使用最有意义的32位
         * 很难想象一个JVM技术需要超过几个位来对非数组对象中的偏移进行编码,
    	 * 但是,为了与该类中的其他方法一致,该方法将其结果报告为一个长值。
         * @see #getInt(Object, long)
         */
        public native long objectFieldOffset(Field f);
    
        /**
         * Report the size in bytes of a native pointer, as stored via {@link
         * #putAddress}.  This value will be either 4 or 8.  Note that the sizes of
         * other primitive types (as stored in native memory blocks) is determined
         * fully by their information content.
         */
        public native int addressSize();
    
        /** The value of {@code addressSize()} */
        public static final int ADDRESS_SIZE = theUnsafe.addressSize();
    
        /**
         * Report the size in bytes of a native memory page (whatever that is).
         * This value will always be a power of two.
         */
        public native int pageSize();
    
    
        /// random trusted operations from JNI:
    
        /**
         * Tell the VM to define a class, without security checks.  By default, the
         * class loader and protection domain come from the caller's class.
         */
        public native Class<?> defineClass(String name, byte[] b, int off, int len,
                                           ClassLoader loader,
                                           ProtectionDomain protectionDomain);
    
        /** Allocate an instance but do not run any constructor.
            Initializes the class if it has not yet been. */
        public native Object allocateInstance(Class<?> cls)
            throws InstantiationException;
    		
    	/** 锁住对象,必须通过monitorExit解锁. */
        public native void monitorEnter(Object o);
        /**
    	 * 解锁对象,必须已经通过#monitorEnter锁住
         */
        public native void monitorExit(Object o);
    
        /**
    	 * 尝试锁住对象,返回值true或false表示是否锁住。如果锁住,必须通过#monitorExit解锁。
         */
        public native boolean tryMonitorEnter(Object o);
    
        /** Throw the exception without telling the verifier. */
        public native void throwException(Throwable ee);
        /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
    	 * 如果对象是期待值,原子性更新Java变量为x
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapObject(Object o, long offset,
                                                         Object expected,
                                                         Object x);
    
        /**
         * Unblock the given thread blocked on <tt>park</tt>, or, if it is
         * not blocked, cause the subsequent call to <tt>park</tt> not to
         * block.  Note: this operation is "unsafe" solely because the
         * caller must somehow ensure that the thread has not been
         * destroyed. Nothing special is usually required to ensure this
         * when called from Java (in which there will ordinarily be a live
         * reference to the thread) but this is not nearly-automatically
         * so when calling from native code.
         * @param thread the thread to unpark.
         *
         */
        public native void unpark(Object thread);
    
        /**
         * Block current thread, returning when a balancing
         * <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has
         * already occurred, or the thread is interrupted, or, if not
         * absolute and time is not zero, the given time nanoseconds have
         * elapsed, or if absolute, the given deadline in milliseconds
         * since Epoch has passed, or spuriously (i.e., returning for no
         * "reason"). Note: This operation is in the Unsafe class only
         * because <tt>unpark</tt> is, so it would be strange to place it
         * elsewhere.
         */
        public native void park(boolean isAbsolute, long time);
    
        // The following contain CAS-based Java implementations used on
        // platforms not supporting native instructions
    
        /**
         * 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 exchanges the given value with 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 newValue new value
         * @return the previous value
         * @since 1.8
         */
        public final int getAndSetInt(Object o, long offset, int newValue) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, newValue));
            return v;
        }
        /**
         * Ensures lack of reordering of loads before the fence
         * with loads or stores after the fence.
         * @since 1.8
         */
        public native void loadFence();
        public native void storeFence();
        public native void fullFence();
    
    }

    AtomicLong

    然后,那我们以AtomicLong为例,看JDK中是如何使用Unsafe的。

    package java.util.concurrent.atomic;
    import sun.misc.Unsafe;
    
    /**
     * 一个AtomicLong被用作原子性增加的序列数字,不是Long的替代,
     * 这个类的确继承了Number,允许使用工具类按接口获得实际数值。
     * @since 1.5
     * @author Doug Lea
     */
    public class AtomicLong extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 1927816293512124184L;
    
        // 这种声明方式,是sun官方推荐的、被信任的。我们在开发时就可以这样使用Unsafe
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        /**
         * 记录虚拟机 是否支持 对Long类型进行无锁的compareAndSwap。
         * 无论虚拟机是否支持,一些构造器应该被控制在Java语言级别,避免显式锁的泄露。
         */
        static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    
        /**
         * 记录底层虚拟机 是否支持 对Long类型进行无锁的CompareAndSet。
         * 这个方法只会调用一次,结果缓存在VM_SUPPORTS_LONG_CAS。
         */
        private static native boolean VMSupportsCS8();
    
        static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile long value;
    
        /**
         * 用给定的初始值创建AtomicLong实例
         * @param initialValue 初始值
         */
        public AtomicLong(long initialValue) {
            value = initialValue;
        }
    
        public AtomicLong() {
        }
    
        /**
         * 返回当前值
         * @return 当前值
         */
        public final long get() {
            return value;
        }
    
        /**
         * 设置给定值
         *
         * @param newValue 新值
         */
        public final void set(long newValue) {
            value = newValue;
        }
    
        /**
         * 最终设定值
         *
         * @param newValue the new value
         * @since 1.6
         */
        public final void lazySet(long newValue) {
            unsafe.putOrderedLong(this, valueOffset, newValue);
        }
    
        /**
         * 原子性设定新值,返回旧值
         * @param newValue 新值
         * @return 旧值
         */
        public final long getAndSet(long newValue) {
            while (true) {
                // 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
                long current = get();
                if (compareAndSet(current, newValue))
                    return current;
            }
        }
    
        /**
         * 如果当前值==期待值,原子性地设置变量为给定更新值
         * @param 期待值
         * @param 要更新的新值
         * @return 如果成功返回true.失败则返回false,表明实际值不等于期待值
         */
        public final boolean compareAndSet(long expect, long update) {
            return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        }
    
        /**
         * 如果当前值==期待值,设定变量为给定更新值
         * 可能假失败,不提供排序保证,所以,只是很少使用的选项。
         * @param expect 期待值
         * @param update 给定更新值
         * @return 成功则返回true
         */
        public final boolean weakCompareAndSet(long expect, long update) {
            return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        }
    
        /**
         * 原子性递增当前值,返回当前值
         * @return the previous value
         */
        public final long getAndIncrement() {
            while (true) {
                // 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
                long current = get(); 
                long next = current + 1;
                if (compareAndSet(current, next))
                    return current;
            }
        }
    
        /**
         * 原子性递减当前值,返回当前值
         *
         * @return the previous value
         */
        public final long getAndDecrement() {
            while (true) {
                // 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
                long current = get(); 
                long next = current - 1;
                if (compareAndSet(current, next))
                    return current;
            }
        }
    
        /**
         * 原子性地将当前值与给定值相加,当前值是未知的,即时获得的。
         *
         * @param delta 加数
         * @return 先前值
         */
        public final long getAndAdd(long delta) {
            while (true) {
                // 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
                long current = get();
                long next = current + delta;
                if (compareAndSet(current, next))
                    return current;
            }
        }
    
        /**
         * 原子性地递增值
         *
         * @return 更新值
         */
        public final long incrementAndGet() {
            for (;;) {
                long current = get();
                long next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    
    }
    

      

      

  • 相关阅读:
    Hackerrank--Emma and sum of products (FFT)
    Hdu 1402 (FFT)
    Hackerrank--Divisibility of Power(Math)
    Hackerrank--Stock Maximize(DP Practice)
    Codeforces 455B
    vim 简明教程(转自飘过的小牛)
    ACdream 1007 (快速幂)
    编写可维护的JS 02
    编写可维护的JS 01
    图片加载-从模糊到清晰
  • 原文地址:https://www.cnblogs.com/zhengwenqiang/p/8119590.html
Copyright © 2011-2022 走看看