zoukankan      html  css  js  c++  java
  • 原子类型的使用&Unsafe&CAS

      在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作)。

      下面以AtomicInteger为例子研究原子类型的线程安全性。

    0. AtomicInteger 原子类型的基本使用

      其实在  AtomicInteger  里面将一些复合操作合并为原子操作,比如常见的"先改值,后取值"、"若相等则替换"、"获取到原值并赋予新值"等操作。

    package cn.qlq.thread.nineteen;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Demo2 {
        public static void main(String[] args) throws InterruptedException {
            AtomicInteger count = new AtomicInteger(0);
            System.out.println(count.incrementAndGet());// 先加后用,相当于++i
            System.out.println(count.getAndIncrement());// 先用后加,相当于i++
            System.out.println(count.intValue());
            System.out.println("===========");
            System.out.println(count.decrementAndGet());// 先减后用,相当于--i
            System.out.println(count.getAndDecrement());// 先用后减,相当于i--
            System.out.println(count.intValue());
    
            System.out.println("===========");
            count.set(10);
            System.out.println(count.addAndGet(5));// 先加后用
            System.out.println(count.getAndAdd(5));// 先用后加
            System.out.println(count.intValue());
    
            System.out.println("===========");
            System.out.println(count.getAndSet(100));// 获取原值,并用新值覆盖
            System.out.println(count.intValue());
    
            System.out.println("===========");
            System.out.println(count.compareAndSet(100, 850));// 如果是100就设为850
            System.out.println(count.compareAndSet(100, 800));// 如果是100就设为800
            System.out.println(count.intValue());
        }
    
    }

    结果:

    1
    1
    2
    ===========
    1
    1
    0
    ===========
    15
    15
    20
    ===========
    20
    100
    ===========
    true
    false
    850

    1. AtomicInteger 原子类型的线程安全性

      AtomicInteger代替Integer和int的 ++效果实现线程安全。

    package cn.qlq.thread.nineteen;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Demo2 {
        public static void main(String[] args) throws InterruptedException {
            Runnable sync4 = new Sync4();
            for (int i = 0; i < 50; i++) {
                new Thread(sync4, "" + i).start();
            }
        }
    }
    
    class Sync4 implements Runnable {
        private AtomicInteger count = new AtomicInteger(0);
    
        public void run() {
            System.out.println(count.getAndIncrement() + "	threadName->" + Thread.currentThread().getName());
        }
    }

    原子类型也不一定是线程安全的:

    package cn.qlq.thread.nineteen;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Demo2 {
        public static void main(String[] args) throws InterruptedException {
            Sync5 sync4 = new Sync5();
            Thread[] threads = new Thread[5];
            for (int i = 0; i < 5; i++) {
                threads[i] = new Thread(sync4);
            }
            for (int i = 0; i < 5; i++) {
                threads[i].start();
            }
        }
    }
    
    class Sync5 implements Runnable {
        private AtomicInteger count = new AtomicInteger(0);
    
        public void run() {
            System.out.println("	threadName->" + Thread.currentThread().getName() + ",加了100后的值是" + count.addAndGet(100));
            count.addAndGet(1);
        }
    }

    结果:

    threadName->Thread-0,加了100后的值是200
    threadName->Thread-1,加了100后的值是100
    threadName->Thread-2,加了100后的值是302
    threadName->Thread-4,加了100后的值是403
    threadName->Thread-3,加了100后的值是503

    原因:

      从结果看出还是发生了线程非安全的问题。因为虽然addAndGet()方法是同步的,但是方法和方法的调用顺序却不是原子的。也就是同一个方法的两次调用addAndGet()中间可能发生线程非安全。解决办法仍然是加同步关键字。

    2. AtomicInteger 原子类型分析

    在分析 AtomicInteger  之前需要先分析  Unsafe 类。因为AtomicXXX中大量的用到了unsafe,而用的是unsafe的CAS操作。

    2.1  Unsafe 类

    Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作

    这个类尽管里面的方法都是public的,但是并没有办法使用它们,JDK API文档也没有提供任何关于这个类的方法的解释。

    1、通过Unsafe类可以分配内存,可以释放内存;(这种方法分配的内存不占用heap内容,也需要手动回收)

    类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存。

    public native long allocateMemory(long l);
    public native long reallocateMemory(long l, long l1);
    public native void freeMemory(long l);
    2、可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;(根据字段的偏移量可以取值与设置值)---对于给定的filed,其内存地址偏移量是唯一的且是固定不变的。

    例如:

    package cn.qlq.thread.nineteen;
    
    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    
    /**
     * Unsafe类的使用
     * Unsafe的直接内存访问:用Unsafe开辟的内存空间不占用Heap空间,当然也不具有自动内存回收功能。做到像C一样自由利用系统内存资源。
     * 
     * @author Administrator
     *
     */
    public class Demo3 {
        public static void main(String[] args) throws Exception {
            test1();
        }
    
        public static long getLocation(Object object) throws Exception {
            Unsafe unsafe = getUnsafeInstance();
            Object[] array = new Object[] { object };
            long baseOffset = unsafe.arrayBaseOffset(Object[].class);// 能够获取数组第一个元素的偏移地址
            int addressSize = unsafe.addressSize();
            long location;
            switch (addressSize) {
    
            case 4:
                location = unsafe.getInt(array, baseOffset);
                break;
            case 8:
                location = unsafe.getLong(array, baseOffset);
                break;
            default:
                throw new Error("unsupported address size: " + addressSize);
            }
            return location;
        }
    
        private static void test1() throws Exception {
            Unsafe u = getUnsafeInstance();
            // 通过Unsafe 构造一个实例,即使构造方法是private声明的
            // Value value = (Value) u.allocateInstance(Value.class);
            Value value = new Value();
            System.out.println(value.getAge());
    
            // 静态成员遍历
            Field staticIntField = Value.class.getDeclaredField("age");
            long staticFieldOffset = u.staticFieldOffset(staticIntField);// 获取偏移量
            System.out.println("staticFieldOffset -> " + staticFieldOffset);
            u.putInt(Value.class, staticFieldOffset, 80);// 修改静态成员遍历的值
            System.out.println(value.getAge());
    
            // 普通成员修改(虽然是private修饰的,根据偏移量也可以取值与设置)
            Field field = Value.class.getDeclaredField("sex");
            int fieldOffset = u.fieldOffset(field);// 获取到偏移量
            System.out.println(u.getInt(value, fieldOffset));
            u.putInt(value, fieldOffset, 2);// 根据偏移量修改值
            System.out.println(value.getSex());
        }
    
        // 反射获取unsafe实例
        public static Unsafe getUnsafeInstance() throws Exception {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        }
    }
    
    class Value {
        private static int age = 25;
        private int sex = 1;
    
        public static int getAge() {
            return age;
        }
    
        public static void setAge(int age) {
            Value.age = age;
        }
    
        public int getSex() {
            return sex;
        }
    
        public void setSex(int sex) {
            this.sex = sex;
        }
    }

    结果:

    25
    staticFieldOffset -> 88
    80
    1
    2

    3、挂起与恢复

    4、CAS比较并交换操作(重要)

        public final native boolean compareAndSwapObject(Object arg0, long arg1, Object arg3, Object arg4);
    
        public final native boolean compareAndSwapInt(Object arg0, long arg1, int arg3, int arg4);
    
        public final native boolean compareAndSwapLong(Object arg0, long arg1, long arg3, long arg5);

      第一个参数是需要更新的对象,第二个参数是字段的偏移量,第三个是  希望field中存在的值,第四个是如果期望值expect与field的当前值相同,设置filed的值为这个新值。

      CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。

      CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

    例如:一个简单的CAS的例子

    package cn.qlq.thread.nineteen;
    
    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    
    /**
     * Unsafe类的使用
     * Unsafe的直接内存访问:用Unsafe开辟的内存空间不占用Heap空间,当然也不具有自动内存回收功能。做到像C一样自由利用系统内存资源。
     * 
     * @author Administrator
     *
     */
    public class Demo4 {
        public static void main(String[] args) throws Exception {
            Value2 value2 = new Value2();
            System.out.println(compAndAwap(value2, 0, 5));
            System.out.println(compAndAwap(value2, 1, 5));
            System.out.println(value2.getSex());
        }
    
        public static boolean compAndAwap(Value2 value, int oldValue, int newValue) throws Exception {
            Unsafe u = getUnsafeInstance();
    
            // 普通成员修改(虽然是private修饰的,根据偏移量也可以取值与设置)
            Field field = Value.class.getDeclaredField("sex");
            int fieldOffset = u.fieldOffset(field);// 获取到偏移量
    
            int int1 = u.getInt(value, fieldOffset);// 判断旧的值与期望值是否相等
            if (int1 == oldValue) {
                u.putInt(value, fieldOffset, newValue);// 根据偏移量修改值
                return true;
            }
    
            return false;
        }
    
        // 反射获取unsafe实例
        public static Unsafe getUnsafeInstance() throws Exception {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        }
    }
    
    class Value2 {
        private int sex = 1;
    
        public int getSex() {
            return sex;
        }
    
        public void setSex(int sex) {
            this.sex = sex;
        }
    }

    结果

    false
    true
    5

    2.2  AtomicInteger源码分析-以addAndGet(int)为例

    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // 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;
        /**
         * Atomically adds the given value to the current value.
         *
         * @param delta the value to add
         * @return the updated value
         */
        public final int addAndGet(int delta) {
            for (;;) {
                int current = get();
                int next = current + delta;
                if (compareAndSet(current, next))
                    return next;
            }
        }
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    ...
    }

      可以看出其也是运用unsafe类的CAS进行比较并交换。由于对于给定的字段,其偏移量是固定且不变的,所以可以用静态代码块初始化 valueOffset (value字段的偏移量)。

      value 字段用volatile声明保存了内存可见性,由于for循环调用cas方法,所以当交换失败的时候会一直重新获取当前值并且进行cas操作。(CAS是原子操作,不可以被打断)。所以用这种机制保证了线程安全性。

    需要注意的是:上面说了,AutomicXXX并不一定线程安全,如果一个方法中多次调用不能保证调用的顺序性。

    2.3  AtomicBoolean源码分析

      AtomicBoolean实际是内部转换为int进行CAS操作。也就是内部维护一个int型的value(在0-1之间进行CAS操作)

    public class AtomicBoolean implements java.io.Serializable {
        private static final long serialVersionUID = 4654671469794556979L;
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
        /**
         * Atomically sets to the given value and returns the previous value.
         *
         * @param newValue the new value
         * @return the previous value
         */
        public final boolean getAndSet(boolean newValue) {
            for (;;) {
                boolean current = get();
                if (compareAndSet(current, newValue))
                    return current;
            }
        }
        public final boolean compareAndSet(boolean expect, boolean update) {
            int e = expect ? 1 : 0;
            int u = update ? 1 : 0;
            return unsafe.compareAndSwapInt(this, valueOffset, e, u);
        }
    。。。
    }

    2.4  AtomicIntegerArray 的使用以及分析

      AtomicIntegerArray用于支持原子整形数组

    package cn.qlq.thread.nineteen;
    
    import java.util.concurrent.atomic.AtomicIntegerArray;
    
    public class Demo5 {
        public static void main(String[] args) throws InterruptedException {
            AtomicIntegerArray array = new AtomicIntegerArray(new int[] { 0, 0 });
            System.out.println(array);
            System.out.println(array.getAndAdd(1, 2));
            System.out.println(array);
        }
    
    }

    结果:

    [0, 0]
    0
    [0, 2]

    源码分析:

    public class AtomicIntegerArray implements java.io.Serializable {
        private static final long serialVersionUID = 2862133569453604235L;
    
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final int base = unsafe.arrayBaseOffset(int[].class);
        private static final int shift;
        private final int[] array;
    
        static {
            int scale = unsafe.arrayIndexScale(int[].class);
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            shift = 31 - Integer.numberOfLeadingZeros(scale);
        }
    
        private long checkedByteOffset(int i) {
            if (i < 0 || i >= array.length)
                throw new IndexOutOfBoundsException("index " + i);
    
            return byteOffset(i);
        }
    
        private static long byteOffset(int i) {
            return ((long) i << shift) + base;
        }
        /**
         * Atomically adds the given value to the element at index {@code i}.
         *
         * @param i the index
         * @param delta the value to add
         * @return the previous value
         */
        public final int getAndAdd(int i, int delta) {
            long offset = checkedByteOffset(i);
            while (true) {
                int current = getRaw(offset);
                if (compareAndSetRaw(offset, current, current + delta))
                    return current;
            }
        }
        private boolean compareAndSetRaw(long offset, int expect, int update) {
            return unsafe.compareAndSwapInt(array, offset, expect, update);
        }
    ...
    }

      由于涉及到了数组操作,所以对数组中元素进行CAS操作的时候需要获取到数组中对应元素的偏移量。而数组中元素的偏移量需要调用 arrayBaseOffset(class) 先获得数组的基偏移量,然后调用arrayIndexScale(class)获取每个元素占用的大小 ,这两个方法结合可以定位到数组中的每个元素与修改每个元素的值(基地址加上每个元素的大小)。

    例如:

    package cn.qlq.thread.nineteen;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    
    import sun.misc.Unsafe;
    
    public class Demo5 {
        public static void main(String[] args) throws Exception {
            int[] array = new int[] { 1, 2, 3 };
            System.out.println(Arrays.toString(array));
            Unsafe unsafeInstance = getUnsafeInstance();
            // 获取数组的基地址
            int arrayBaseOffset = unsafeInstance.arrayBaseOffset(int[].class);
            System.out.println(arrayBaseOffset);
    
            // 比例值--每个元素占的大小
            int scale = unsafeInstance.arrayIndexScale(int[].class);
            System.out.println(scale);
    
            // 修改index为1元素的值为22
            unsafeInstance.putInt(array, arrayBaseOffset + 1 * scale, 22);
    
            // 采用元素加地址偏移量或者元素的值
            for (int i = 0; i < array.length; i++) {
                System.out.println(unsafeInstance.getInt(array, arrayBaseOffset + i * scale));
            }
        }
    
        // 反射获取unsafe实例
        public static Unsafe getUnsafeInstance() throws Exception {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        }
    }

    结果:

    [1, 2, 3]
    16
    4
    1
    22
    3

  • 相关阅读:
    链表与顺序表
    js对table的动态操作
    关于float的内部结构
    spring bean的生命周期
    浅谈(吐槽)自己
    java缓存机制(上)
    Verilog经典输入控制/激励信号模板1
    verilog中的for循环问题
    2015,welcome!!!
    (转)Quartus II和Modelsim的联合仿真(详细)
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/10235084.html
Copyright © 2011-2022 走看看