并发之ATOMIC原子操作--Unsafe解析

Atomic 类的原子操作是依赖java中的魔法类sun.misc.Unsafe来实现的,而这个类为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。

获取Unsafe的实例

查看Unsafe的源码我们会发现它提供了一个getUnsafe()的静态方法。

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

如果直接调用这个方法会抛出一个SecurityException异常,这是因为Unsafe仅供java内部类使用,外部类不应该使用它。通过反射拿到它的实例

public class UnsafeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
    }
}

 

使用Unsafe实例化一个类

假如我们有一个简单的类如下:

class User {
    int age;

    public User() {
        this.age = 10;
    }
}

如果我们通过构造方法实例化这个类,age属性将会返回10。

User user1 = new User();
// 打印10
System.out.println(user1.age);

如果我们调用Unsafe来实例化呢?

User user2 = (User) unsafe.allocateInstance(User.class);
// 打印0
System.out.println(user2.age);

age将返回0,因为 Unsafe.allocateInstance() 只会给对象分配内存,并不会调用构造方法,所以这里只会返回int类型的默认值0。

 

修改私有字段的值

使用Unsafe的putXXX()方法,我们可以修改任意私有字段的值。

public class UnsafeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        User user = new User();
        Field age = user.getClass().getDeclaredField("age");
        unsafe.putInt(user, unsafe.objectFieldOffset(age), 20);

        // 打印20
        System.out.println(user.getAge());
    }
}

class User {
    private int age;

    public User() {
        this.age = 10;
    }

    public int getAge() {
        return age;
    }
}

一旦我们通过反射调用得到字段age,我们就可以使用Unsafe将其值更改为任何其他int值。(当然,这里也可以通过反射直接修改)

 

抛出checked异常

我们知道如果代码抛出了checked异常,要不就使用try...catch捕获它,要不就在方法签名上定义这个异常,但是,通过Unsafe我们可以抛出一个checked异常,同时却不用捕获或在方法签名上定义它。

// 使用正常方式抛出IOException需要定义在方法签名上往外抛
public static void readFile() throws IOException {
    throw new IOException();
}
// 使用Unsafe抛出异常不需要定义在方法签名上往外抛
public static void readFileUnsafe() {
    unsafe.throwException(new IOException());
}

 

使用堆外内存

如果进程在运行过程中JVM上的内存不足了,会导致频繁的进行GC。理想情况下,我们可以考虑使用堆外内存,这是一块不受JVM管理的内存。

使用Unsafe的allocateMemory()我们可以直接在堆外分配内存,这可能非常有用,但我们要记住,这个内存不受JVM管理,因此我们要调用freeMemory()方法手动释放它。

假设我们要在堆外创建一个巨大的int数组,我们可以使用allocateMemory()方法来实现:

class OffHeapArray {
    // 一个int等于4个字节
    private static final int INT = 4;
    private long size;
    private long address;

    private static Unsafe unsafe;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 构造方法,分配内存
    public OffHeapArray(long size) {
        this.size = size;
        // 参数字节数
        address = unsafe.allocateMemory(size * INT);
    }
    
    // 获取指定索引处的元素
    public int get(long i) {
        return unsafe.getInt(address + i * INT);
    }
    // 设置指定索引处的元素
    public void set(long i, int value) {
        unsafe.putInt(address + i * INT, value);
    }
    // 元素个数
    public long size() {
        return size;
    }
    // 释放堆外内存
    public void freeMemory() {
        unsafe.freeMemory(address);
    }
}

在构造方法中调用allocateMemory()分配内存,在使用完成后调用freeMemory()释放内存。

使用方式如下:

OffHeapArray offHeapArray = new OffHeapArray(4);
offHeapArray.set(0, 1);
offHeapArray.set(1, 2);
offHeapArray.set(2, 3);
offHeapArray.set(3, 4);
offHeapArray.set(2, 5); // 在索引2的位置重复放入元素

int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
    sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);

offHeapArray.freeMemory();

最后,一定要记得调用freeMemory()将内存释放回操作系统。

 

CompareAndSwap操作

JUC下面大量使用了CAS操作,它们的底层是调用的Unsafe的CompareAndSwapXXX()方法。这种方式广泛运用于无锁算法,与java中标准的悲观锁机制相比,它可以利用CAS处理器指令提供极大的加速。

比如,我们可以基于Unsafe的compareAndSwapInt()方法构建线程安全的计数器。

class Counter {
    private volatile int count = 0;

    private static long offset;
    private static Unsafe unsafe;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void increment() {
        int before = count;
        // 失败了就重试直到成功为止
        while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {
            before = count;
        }
    }

    public int getCount() {
        return count;
    }
}

我们定义了一个volatile的字段count,以便对它的修改所有线程都可见,并在类加载的时候获取count在类中的偏移地址。

在increment()方法中,我们通过调用Unsafe的compareAndSwapInt()方法来尝试更新之前获取到的count的值,如果它没有被其它线程更新过,则更新成功,否则不断重试直到成功为止。

我们可以通过使用多个线程来测试我们的代码:

Counter counter = new Counter();
ExecutorService threadPool = Executors.newFixedThreadPool(100);

// 起100个线程,每个线程自增10000次
IntStream.range(0, 100)
    .forEach(i->threadPool.submit(()->IntStream.range(0, 10000)
        .forEach(j->counter.increment())));

threadPool.shutdown();

Thread.sleep(2000);

// 打印1000000
System.out.println(counter.getCount());

 

park/unpark

JVM在上下文切换的时候使用了Unsafe中的两个非常牛逼的方法park()和unpark()。

当一个线程正在等待某个操作时,JVM调用Unsafe的park()方法来阻塞此线程。

当阻塞中的线程需要再次运行时,JVM调用Unsafe的unpark()方法来唤醒此线程。

我们之前在分析java中的集合时看到了大量的LockSupport.park()/unpark(),它们底层都是调用的Unsafe的这两个方法。

总结

使用Unsafe几乎可以操作一切:

(1)实例化一个类;

(2)修改私有字段的值;

(3)抛出checked异常;

(4)使用堆外内存;

(5)CAS操作;

(6)阻塞/唤醒线程;

 

彩蛋

论实例化一个类的方式?

(1)通过构造方法实例化一个类;

(2)通过Class实例化一个类;

(3)通过反射实例化一个类;

(4)通过克隆实例化一个类;

(5)通过反序列化实例化一个类;

(6)通过Unsafe实例化一个类;

public class InstantialTest {

    private static Unsafe unsafe;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws Exception {
        // 1. 构造方法
        User user1 = new User();
        // 2. Class,里面实际也是反射
        User user2 = User.class.newInstance();
        // 3. 反射
        User user3 = User.class.getConstructor().newInstance();
        // 4. 克隆
        User user4 = (User) user1.clone();
        // 5. 反序列化
        User user5 = unserialize(user1);
        // 6. Unsafe
        User user6 = (User) unsafe.allocateInstance(User.class);

        System.out.println(user1.age);
        System.out.println(user2.age);
        System.out.println(user3.age);
        System.out.println(user4.age);
        System.out.println(user5.age);
        System.out.println(user6.age);
    }

    private static User unserialize(User user1) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://object.txt"));
        oos.writeObject(user1);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://object.txt"));
        // 反序列化
        User user5 = (User) ois.readObject();
        ois.close();
        return user5;
    }

    static class User implements Cloneable, Serializable {
        private int age;

        public User() {
            this.age = 10;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
}

 

附 Unsafe.class:

并发之ATOMIC原子操作--Unsafe解析
package sun.misc;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

public final class Unsafe
{
  static
  {
    registerNatives();
    Reflection.registerMethodsToFilter(Unsafe.class, new String[] { "getUnsafe" });
  }
  
  private static final Unsafe theUnsafe = new Unsafe();
  public static final int INVALID_FIELD_OFFSET = -1;
  
  @CallerSensitive
  public static Unsafe getUnsafe()
  {
    Class localClass = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(localClass.getClassLoader())) {
      throw new SecurityException("Unsafe");
    }
    return theUnsafe;
  }
  
  @Deprecated
  public int getInt(Object paramObject, int paramInt)
  {
    return getInt(paramObject, paramInt);
  }
  
  @Deprecated
  public void putInt(Object paramObject, int paramInt1, int paramInt2)
  {
    putInt(paramObject, paramInt1, paramInt2);
  }
  
  @Deprecated
  public Object getObject(Object paramObject, int paramInt)
  {
    return getObject(paramObject, paramInt);
  }
  
  @Deprecated
  public void putObject(Object paramObject1, int paramInt, Object paramObject2)
  {
    putObject(paramObject1, paramInt, paramObject2);
  }
  
  @Deprecated
  public boolean getBoolean(Object paramObject, int paramInt)
  {
    return getBoolean(paramObject, paramInt);
  }
  
  @Deprecated
  public void putBoolean(Object paramObject, int paramInt, boolean paramBoolean)
  {
    putBoolean(paramObject, paramInt, paramBoolean);
  }
  
  @Deprecated
  public byte getByte(Object paramObject, int paramInt)
  {
    return getByte(paramObject, paramInt);
  }
  
  @Deprecated
  public void putByte(Object paramObject, int paramInt, byte paramByte)
  {
    putByte(paramObject, paramInt, paramByte);
  }
  
  @Deprecated
  public short getShort(Object paramObject, int paramInt)
  {
    return getShort(paramObject, paramInt);
  }
  
  @Deprecated
  public void putShort(Object paramObject, int paramInt, short paramShort)
  {
    putShort(paramObject, paramInt, paramShort);
  }
  
  @Deprecated
  public char getChar(Object paramObject, int paramInt)
  {
    return getChar(paramObject, paramInt);
  }
  
  @Deprecated
  public void putChar(Object paramObject, int paramInt, char paramChar)
  {
    putChar(paramObject, paramInt, paramChar);
  }
  
  @Deprecated
  public long getLong(Object paramObject, int paramInt)
  {
    return getLong(paramObject, paramInt);
  }
  
  @Deprecated
  public void putLong(Object paramObject, int paramInt, long paramLong)
  {
    putLong(paramObject, paramInt, paramLong);
  }
  
  @Deprecated
  public float getFloat(Object paramObject, int paramInt)
  {
    return getFloat(paramObject, paramInt);
  }
  
  @Deprecated
  public void putFloat(Object paramObject, int paramInt, float paramFloat)
  {
    putFloat(paramObject, paramInt, paramFloat);
  }
  
  @Deprecated
  public double getDouble(Object paramObject, int paramInt)
  {
    return getDouble(paramObject, paramInt);
  }
  
  @Deprecated
  public void putDouble(Object paramObject, int paramInt, double paramDouble)
  {
    putDouble(paramObject, paramInt, paramDouble);
  }
  
  public void setMemory(long paramLong1, long paramLong2, byte paramByte)
  {
    setMemory(null, paramLong1, paramLong2, paramByte);
  }
  
  public void copyMemory(long paramLong1, long paramLong2, long paramLong3)
  {
    copyMemory(null, paramLong1, null, paramLong2, paramLong3);
  }
  
  @Deprecated
  public int fieldOffset(Field paramField)
  {
    if (Modifier.isStatic(paramField.getModifiers())) {
      return (int)staticFieldOffset(paramField);
    }
    return (int)objectFieldOffset(paramField);
  }
  
  @Deprecated
  public Object staticFieldBase(Class<?> paramClass)
  {
    Field[] arrayOfField = paramClass.getDeclaredFields();
    for (int i = 0; i < arrayOfField.length; i++) {
      if (Modifier.isStatic(arrayOfField[i].getModifiers())) {
        return staticFieldBase(arrayOfField[i]);
      }
    }
    return null;
  }
  
  public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(boolean[].class);
  public static final int ARRAY_BYTE_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(byte[].class);
  public static final int ARRAY_SHORT_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(short[].class);
  public static final int ARRAY_CHAR_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(char[].class);
  public static final int ARRAY_INT_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(int[].class);
  public static final int ARRAY_LONG_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(long[].class);
  public static final int ARRAY_FLOAT_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(float[].class);
  public static final int ARRAY_DOUBLE_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(double[].class);
  public static final int ARRAY_OBJECT_BASE_OFFSET = theUnsafe
    .arrayBaseOffset(Object[].class);
  public static final int ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe
    .arrayIndexScale(boolean[].class);
  public static final int ARRAY_BYTE_INDEX_SCALE = theUnsafe
    .arrayIndexScale(byte[].class);
  public static final int ARRAY_SHORT_INDEX_SCALE = theUnsafe
    .arrayIndexScale(short[].class);
  public static final int ARRAY_CHAR_INDEX_SCALE = theUnsafe
    .arrayIndexScale(char[].class);
  public static final int ARRAY_INT_INDEX_SCALE = theUnsafe
    .arrayIndexScale(int[].class);
  public static final int ARRAY_LONG_INDEX_SCALE = theUnsafe
    .arrayIndexScale(long[].class);
  public static final int ARRAY_FLOAT_INDEX_SCALE = theUnsafe
    .arrayIndexScale(float[].class);
  public static final int ARRAY_DOUBLE_INDEX_SCALE = theUnsafe
    .arrayIndexScale(double[].class);
  public static final int ARRAY_OBJECT_INDEX_SCALE = theUnsafe
    .arrayIndexScale(Object[].class);
  public static final int ADDRESS_SIZE = theUnsafe.addressSize();
  
  public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
    {
      i = getIntVolatile(paramObject, paramLong);
    } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
    return i;
  }
  
  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
    {
      l = getLongVolatile(paramObject, paramLong1);
    } while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
    return l;
  }
  
  public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
    {
      i = getIntVolatile(paramObject, paramLong);
    } while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
    return i;
  }
  
  public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
    {
      l = getLongVolatile(paramObject, paramLong1);
    } while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
    return l;
  }
  
  public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
  {
    Object localObject;
    do
    {
      localObject = getObjectVolatile(paramObject1, paramLong);
    } while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
    return localObject;
  }
  
  private static void throwIllegalAccessError()
  {
    throw new IllegalAccessError();
  }
  
  private static native void registerNatives();
  
  public native int getInt(Object paramObject, long paramLong);
  
  public native void putInt(Object paramObject, long paramLong, int paramInt);
  
  public native Object getObject(Object paramObject, long paramLong);
  
  public native void putObject(Object paramObject1, long paramLong, Object paramObject2);
  
  public native boolean getBoolean(Object paramObject, long paramLong);
  
  public native void putBoolean(Object paramObject, long paramLong, boolean paramBoolean);
  
  public native byte getByte(Object paramObject, long paramLong);
  
  public native void putByte(Object paramObject, long paramLong, byte paramByte);
  
  public native short getShort(Object paramObject, long paramLong);
  
  public native void putShort(Object paramObject, long paramLong, short paramShort);
  
  public native char getChar(Object paramObject, long paramLong);
  
  public native void putChar(Object paramObject, long paramLong, char paramChar);
  
  public native long getLong(Object paramObject, long paramLong);
  
  public native void putLong(Object paramObject, long paramLong1, long paramLong2);
  
  public native float getFloat(Object paramObject, long paramLong);
  
  public native void putFloat(Object paramObject, long paramLong, float paramFloat);
  
  public native double getDouble(Object paramObject, long paramLong);
  
  public native void putDouble(Object paramObject, long paramLong, double paramDouble);
  
  public native byte getByte(long paramLong);
  
  public native void putByte(long paramLong, byte paramByte);
  
  public native short getShort(long paramLong);
  
  public native void putShort(long paramLong, short paramShort);
  
  public native char getChar(long paramLong);
  
  public native void putChar(long paramLong, char paramChar);
  
  public native int getInt(long paramLong);
  
  public native void putInt(long paramLong, int paramInt);
  
  public native long getLong(long paramLong);
  
  public native void putLong(long paramLong1, long paramLong2);
  
  public native float getFloat(long paramLong);
  
  public native void putFloat(long paramLong, float paramFloat);
  
  public native double getDouble(long paramLong);
  
  public native void putDouble(long paramLong, double paramDouble);
  
  public native long getAddress(long paramLong);
  
  public native void putAddress(long paramLong1, long paramLong2);
  
  public native long allocateMemory(long paramLong);
  
  public native long reallocateMemory(long paramLong1, long paramLong2);
  
  public native void setMemory(Object paramObject, long paramLong1, long paramLong2, byte paramByte);
  
  public native void copyMemory(Object paramObject1, long paramLong1, Object paramObject2, long paramLong2, long paramLong3);
  
  public native void freeMemory(long paramLong);
  
  public native long staticFieldOffset(Field paramField);
  
  public native long objectFieldOffset(Field paramField);
  
  public native Object staticFieldBase(Field paramField);
  
  public native boolean shouldBeInitialized(Class<?> paramClass);
  
  public native void ensureClassInitialized(Class<?> paramClass);
  
  public native int arrayBaseOffset(Class<?> paramClass);
  
  public native int arrayIndexScale(Class<?> paramClass);
  
  public native int addressSize();
  
  public native int pageSize();
  
  public native Class<?> defineClass(String paramString, byte[] paramArrayOfByte, int paramInt1, int paramInt2, ClassLoader paramClassLoader, ProtectionDomain paramProtectionDomain);
  
  public native Class<?> defineAnonymousClass(Class<?> paramClass, byte[] paramArrayOfByte, Object[] paramArrayOfObject);
  
  public native Object allocateInstance(Class<?> paramClass)
    throws InstantiationException;
  
  @Deprecated
  public native void monitorEnter(Object paramObject);
  
  @Deprecated
  public native void monitorExit(Object paramObject);
  
  @Deprecated
  public native boolean tryMonitorEnter(Object paramObject);
  
  public native void throwException(Throwable paramThrowable);
  
  public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
  
  public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
  
  public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
  
  public native Object getObjectVolatile(Object paramObject, long paramLong);
  
  public native void putObjectVolatile(Object paramObject1, long paramLong, Object paramObject2);
  
  public native int getIntVolatile(Object paramObject, long paramLong);
  
  public native void putIntVolatile(Object paramObject, long paramLong, int paramInt);
  
  public native boolean getBooleanVolatile(Object paramObject, long paramLong);
  
  public native void putBooleanVolatile(Object paramObject, long paramLong, boolean paramBoolean);
  
  public native byte getByteVolatile(Object paramObject, long paramLong);
  
  public native void putByteVolatile(Object paramObject, long paramLong, byte paramByte);
  
  public native short getShortVolatile(Object paramObject, long paramLong);
  
  public native void putShortVolatile(Object paramObject, long paramLong, short paramShort);
  
  public native char getCharVolatile(Object paramObject, long paramLong);
  
  public native void putCharVolatile(Object paramObject, long paramLong, char paramChar);
  
  public native long getLongVolatile(Object paramObject, long paramLong);
  
  public native void putLongVolatile(Object paramObject, long paramLong1, long paramLong2);
  
  public native float getFloatVolatile(Object paramObject, long paramLong);
  
  public native void putFloatVolatile(Object paramObject, long paramLong, float paramFloat);
  
  public native double getDoubleVolatile(Object paramObject, long paramLong);
  
  public native void putDoubleVolatile(Object paramObject, long paramLong, double paramDouble);
  
  public native void putOrderedObject(Object paramObject1, long paramLong, Object paramObject2);
  
  public native void putOrderedInt(Object paramObject, long paramLong, int paramInt);
  
  public native void putOrderedLong(Object paramObject, long paramLong1, long paramLong2);
  
  public native void unpark(Object paramObject);
  
  public native void park(boolean paramBoolean, long paramLong);
  
  public native int getLoadAverage(double[] paramArrayOfDouble, int paramInt);
  
  public native void loadFence();
  
  public native void storeFence();
  
  public native void fullFence();
}
View Code

 

原文链接:https://www.cnblogs.com/tong-yuan/p/Unsafe.html

上一篇:java - CAS及CAS底层原理


下一篇:go语言中的内存对齐