Unsafe是一个sun.misc包下的类。Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能。
1. 获取Unsafe对象
由于JVM默认不允许外部使用Unsafe1
2
3
4
5
6
7
8
9
10
11
12private static final Unsafe theUnsafe;
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 判断当前类加载器不为系统加载器,抛出一个安全异常
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}
需要使用反射获取Unsafe内部持有的Unsafe对象theUnsafe1
2
3
4
5
6
7
8
9
10private static Unsafe UNSAFE;
static {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        UNSAFE = (Unsafe) theUnsafe.get(null);
    } catch (Exception e) {
        CLogger.writeError("title", e);
    }
}
2. Unsafe功能
a. 实例化类
1  | /**  | 
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14public class UnsafeAllocateInstanceTest {
    static class User {
        int age;
        public User() {
            age = 10;
        }
    }
    public static void main(String[] args) {
        User u1 = new User();
        System.out.println(u1.age); // 打印结果为10
        User u2 = (User) UNSAFE.allocateInstance(User.class);
        System.out.println(u2.age); // 打印结果为0
    }
}
b. 获取内存偏移地址
1  | /**  | 
c. 抛出checked异常,而且不在方法签名中定义异常
1  | /**  | 
d. 使用堆外内存
1  | 
  | 
测试方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45class OffHeapArray {
    // 一个int等于4个字节
    private static final int INT = 4;
    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();
        }
    }     
    private long size;
    private long address;
    // 构造方法,分配内存
    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);
    }
}
e. CAS方法
CAS (compare and swap)比较并替换,这是一个原子操作,底层实现使用了汇编中cmpxchange指令,传入4个参数:要修改的对象,要修改数据在对象类的相对偏移地址,预期值,想要设置的值。
预期值的作用是使用当前内存中最新值和传入的预期值进行比较,如果相同,则要修改的数据没有发生修改(此处可能会有ABA问题,JUC中提供了两个类处理此类问题,后面详述),直接用想要设置的值进行替换。1
2
3
4
5
6
7
8
9
10
11
12/** 
 * 使用CAS方式替换一个对象变量
 */
   public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
/** 
 * 使用CAS方式替换一个int类型变量
 */
   public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
/** 
 * 使用CAS方式替换一个long类型变量
 */
   public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
f. park / unpark
JVM在上下文切换的时候使用了Unsafe中的方法park()和unpark()。在LockSupport.park()/unpark()方法中,它们底层都是调用的Unsafe的这两个方法。
1  | /**  | 
5. 使用Unsafe的CAS功能
使用Unsafe的CAS功能实现一个原子类AtomicInteger1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65public class MyAtomicInteger {
    private static Unsafe UNSAFE;
    private static int amount = 0;
    private static MyAtomicInteger amountMAI = new MyAtomicInteger();
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            CLogger.writeError("title", e);
        }
    }
    private volatile int val;
    public static void main(String[] args) throws InterruptedException {
        // right way
        for (int i = 0; i < 10000; i++) {
            Thread title = new Thread(new Runnable() {
                
                public void run() {
                    try {
                        for (int i1 = 0; i1 < 1000; i1++) {
                            amountMAI.incrAndGet();
                        }
                    } catch (NoSuchFieldException e) {
                        CLogger.writeError("title", e);
                    }
                }
            });
            title.start();
            // 等待线程执行结束
            title.join();
        }
        // 10000000
        System.out.println(amountMAI.getCount());
        // wrong way
//        for (int i = 0; i < 10000; i++) {
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    for (int i1 = 0; i1 < 1000; i1++) {
//                        amount++;
//                    }
//                }
//            }).start();
//        }
//        // 281, 790
//        System.out.println(amount);
    }
    public int incrAndGet() throws NoSuchFieldException {
        // 使用Unsafe提供的compareAndSwapInt方法,如果设值失败则一直循环,直到设值成功
        while (!UNSAFE.compareAndSwapInt(this, UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("val")), this.val, val + 1))
            ;
        return val;
    }
    public int getCount() {
        return val;
    }
}