J.U.C之Unsafe

J.U.C之Unsafe

Unsafe 概述

Unsafe类是在sun.misc包下,不属于Java标准。

Unsafe类是很多Java的基础类库是基类,Netty、Cassandra、Hadoop、Kafka等

Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用

非不安全的类

UnSafe提供了硬件级别的原子操作,提高了Java对底层操作的能力。但Java是类型安全的语言,Unsafe类使Java语言拥有了像C语言的指针一样的操作内存空间的能力,当然同时也带来了指针的问题。因此被认为是不安全的类。

过度的使用Unsafe类会使得程序出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类。

获取Unsafe受限

  • 无法通过new Unsafe() 获取
//Unsafe 被设计成单例模式,构造方法私有。
private Unsafe() {
}
  • 无法通过Unsafe.getUnsafe() 获取
//getUnsafe 被设计成只能从引导类加载器(bootstrap class loader)加载。
//非引导类加载器(bootstrap class loader)使用Unsafe.getUnsafe() 方法会抛出 SecurityException 异常。
public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass(2);
        if (var0.getClassLoader() != null) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
}

获取Unsafe方式

方式一

可以令代码 " 受信任 "。运行程序时,通过 JVM 参数设置 bootclasspath 选项,指定系统类路径加上使用的一个 Unsafe 路径。

java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient

方式二

通过 Java 反射机制

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

Unsafe 使用场景

Unsafe中一共有82个public native修饰的方法,还有几十个基于这82个public native方法的其他方法。

类、对象和变量相关

实例化对象

//创建对象,绕过对象init(对象实例化),安全检查器。如果类未被初始化,将初始化类。
public native Object allocateInstance(Class cls) throws InstantiationException;
绕过对象init(对象实例化)

不会执行构造方法,同时也不会执行实例方法块

绕过对象创建安全检查器

针对没有public 的构造方法类时,也可以实例化对象,变得非常有用。

栗子
public class TestA {

    public static int b=2;
    private int a = 0;

    {
        a=2;
    }

    public TestA() {
        a = 1;
    }

    public int getA() {
        return a;
    }
}

@Test
public void  createTestA() throws Exception {
    /** 使用构造器 **/
    TestA constructorA = new TestA();
    System.out.println(constructorA.getA()); //print 1

    /** 使用反射 **/
    TestA reflectionA = TestA.class.newInstance();
    System.out.println(reflectionA.getA()); //print 1

    /** 使用allocateInstance 类会被加载初始化(会执行静态代码块),但类的对象不会初始化(不会执行方法块,和构造函数)**/
    TestA unsafeA = (TestA) unsafe.allocateInstance(TestA.class);
    System.out.println(unsafeA.getA()); //print 0
    System.out.println(unsafeA.b);  //print 2
}

操作对象属性

Unsafe 可用于绕过安全的常用技术,直接修改内存变量。

相对于反射也可以实现相同的功能。但是 Unsafe 可以修改任何对象,甚至没有这些对象的引用。

//获得对象的字段偏移量 
public native long objectFieldOffset(Field f); 
//获得给定对象地址偏移量的int值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);
//获得给定对象地址偏移量的值
public native Object getObject(Object o, long offset);
//设置给定对象地址偏移量的值
public native void putObject(Object o, long offset, Object x);
栗子
public class TestA {

    private int ACCESS_ALLOWED = 1;

    //在正常情况下,giveAccess 总会返回 false。
    public boolean giveAccess() {
        return 40 == ACCESS_ALLOWED;
    }
}

@Test
public void updateFiled() throws Exception {
    /** 默认情况下打印false **/
    TestA constructorA = new TestA();
    System.out.println(constructorA.giveAccess()); //print false

    /** 使用allocateInstance 类会被加载初始化(会执行静态代码块),但类的对象不会初始化(不会执行方法块,和构造函数)**/
    TestA unsafeA = (TestA) unsafe.allocateInstance(TestA.class);
    /** 通过属性获得ACCESS_ALLOWED 属性 Field对象 **/
    Field unsafeAField = unsafeA.getClass().getDeclaredField("ACCESS_ALLOWED");
    /** 使用objectFieldOffset获取属性在内存种的偏移  **/
    /** 使用putInt修改对象内存中属性值  **/
    unsafe.putInt(unsafeA, unsafe.objectFieldOffset(unsafeAField), 40);
    /** 使用getInt获取对象内存中属性值  **/
    System.out.println(unsafe.getInt(unsafeA, unsafe.objectFieldOffset(unsafeAField)));//print 40
    System.out.println(unsafeA.giveAccess()); //print true
}

动态创建类

//动态创建类
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//动态创建类
public native Class defineClass(String var1, byte[] var2, int var3, int var4);
//动态创建匿名类
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
栗子
public class TestB {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

@Test
public void createClass() throws Exception {
    /** 获取TestB.class字节码 **/
    byte[] classContents = getClassContent();
    /** 动态创建class,创建动态Class名称为TestB,这里设置null是因为该class已经被加载了设置成null**/
    Class c = (Class) unsafe.defineClass(null, classContents, 0, classContents.length, this.getClass().getClassLoader(), null);
    System.out.println(c.getMethod("getA").invoke(c.newInstance(), null));
}

//获取TestB.class字节码
private static byte[] getClassContent() throws Exception {
    return FileCopyUtils.copyToByteArray(UnSafeTest.class.getResource("TestB.class").openStream());
}

类初始化

//判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class c);
//确保类被初始化
public native void ensureClassInitialized(Class c);
栗子
public class TestA {

    public static int b=2;
    private int a = 0;

    private int ACCESS_ALLOWED = 1;

    public boolean giveAccess() {
        return 40 == ACCESS_ALLOWED;
    }

    static {
        System.out.println("class init");
    }

    {
        a=2;
    }

    public TestA() {
        a = 1;
    }

    public int getA() {
        return a;
    }
}

/**
 * 类初始化
 *
 * @throws Exception
 */
@Test
public void initClass() throws Exception {
    System.out.println(unsafe.shouldBeInitialized(TestA.class)); //print true
    unsafe.ensureClassInitialized(TestA.class);  //print class init
    System.out.println(unsafe.shouldBeInitialized(TestA.class)); //print false
}

获取类属性偏移量

//静态属性的偏移量
public native long staticFieldOffset(Field f);
栗子
public class Person {
    public static String NAME = "doge";
    public String age;
}
//获取类属性偏移量
@Test
public void staticFieldOffset() throws Exception {
    Class personClass = Person.class;
    Field name = personClass.getField("NAME");
    Field age = personClass.getField("age");

    try {
        System.out.println("staticFieldOffset name -->" + unsafe.staticFieldOffset(name)); //print staticFieldOffset name -->104
        null
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    try {
        System.out.println("staticFieldOffset age -->" + unsafe.staticFieldOffset(age));
    } catch (Exception e) {
        System.out.println(e.getMessage()); // print null
    }
}

获取类属性归属Class

public native Object staticFieldBase(Field f);
栗子
public class Person {
    public static String NAME = "doge";
    public String age;
}
@Test
public void staticFieldBase() throws Exception {
    Class personClass = Person.class;
    Field name = personClass.getField("NAME");
    Field age = personClass.getField("age");

    try {
        System.out.println("staticFieldOffset name -->" + unsafe.staticFieldBase(name));
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    try {
        System.out.println("staticFieldOffset age -->" + unsafe.staticFieldBase(age));
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

CAS

cas全称compareAndSwap,内存偏移地址 offset,预期值 expected,新值 x。如果变量在当前时刻的值和预期值 expected 相等,尝试将变量的值更新为 x。如果更新成功,返回 true;否则,返回 false。

//更新变量值为x,如果当前值为expected
//o:对象 offset:偏移量 expected:期望值 x:新值

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
  
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
  
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

JDK 1.8 中基于 CAS 扩展

基于CAS+FOR循环实现无锁方式保证多线程一致性。

//增加
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;
}
  
public final long getAndAddLong(Object o, long offset, long delta) {
 long v;
 do {
 v = getLongVolatile(o, offset);
 } while (!compareAndSwapLong(o, offset, v, v + delta));
 return v;
}
//设置
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;
}
  
public final long getAndSetLong(Object o, long offset, long newValue) {
 long v;
 do {
 v = getLongVolatile(o, offset);
 } while (!compareAndSwapLong(o, offset, v, newValue));
 return v;
}

public final Object getAndSetObject(Object o, long offset, Object newValue) {
 Object v;
 do {
 v = getObjectVolatile(o, offset);
 } while (!compareAndSwapObject(o, offset, v, newValue));
 return v;

ABA 问题

在多线程环境中,使用 CAS,如果一个线程对变量修改 2 次,第 2 次修改后的值和第 1 次修改前的值相同,其他线程对此一无所知,这类现象称为 ABA 问题。

ABA 问题可以使用 JDK 并发包中的 AtomicStampedReferenceAtomicMarkableReference 解决。

  • 这两个类是通过版本号(时间戳)来解决 ABA 问题的,也可以使用版本号(verison)来解决 ABA,即乐观锁每次在执行数据的修改操作时,都带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则执行失败。

栗子

CASCounter 通过CAS+FOR实现了无锁的方式保证多线程一致性

public class CASCounter {

    private volatile long counter = 0;
    private Unsafe unsafe;
    private long offset;

    public CASCounter() throws Exception {
        unsafe = getUnsafe();
        offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
    }

    public void increment() {
        long before = counter;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = counter;
        }
    }

    public long getCounter() {
        return counter;
    }

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

测试CASCounter 保证多线程一致性

@Test
public void cas() {
    final TestB b = new TestB();
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            b.counter.increment();
        }
    });
    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            b.counter.increment();
        }
    });
    Thread threadC = new Thread(new Runnable() {
        @Override
        public void run() {
            b.counter.increment();
        }
    });
    threadA.start();
    threadB.start();
    threadC.start();
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(b.counter.getCounter()); //print 3
}


private static class TestB {
    private CASCounter counter;

    public TestB() {
        try {
            counter = new CASCounter();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程挂起与恢复

//传入的线程将归还许可
public native void unpark(Thread jthread);  

//当前线程获取许可,成功则返回,失败则阻塞,并提供了超时机制。
//阻塞响应中断
//isAbsolute 参数是指明时间是否是绝对时间
//isAbsolute=true时  时间是绝对时间,时间单位为ms
//isAbsolute=flase时 时间是相对时间,时间单位为ns
public native void park(boolean isAbsolute, long time); // 

park

表示获取许可,默认情况下线程中不存在许可。当前线程第一次使用Unsafe 调用park方法时(如果未调用unpark)会导致当前线程阻塞。

isAbsolute

参数isAbsolute用来表示是否为绝对时间

isAbsolute=false
  • time等于0,如果没有获取许可线程会一直阻塞
  • time大于0,这里的单位是ns(纳秒),如果线程阻塞,time ns(纳秒)后会从阻塞中唤醒返回
    @Test
    public void park1(){
        unsafe.park(false, 0);
    }
    
    @Test
    public void park2(){
        long star=System.currentTimeMillis();
        unsafe.park(false, 5000000000L);
        System.out.println(System.currentTimeMillis()-star);
    }
isAbsolute=true
  • time大于0,这里的单位是ms(毫秒),如果线程阻塞,time ms(毫秒)后会从阻塞中唤醒返回
  • time 小于等于0,则直接返回
    @Test
    public void park3(){
        long star=System.currentTimeMillis();
        unsafe.park(true, System.currentTimeMillis()+5000L);
        System.out.println(System.currentTimeMillis()-star);
    }
    
    @Test
    public void park4(){
        unsafe.park(true, 0L);
    }

unpark

表示归还许可,同一个线程多次归还许可只和一次归还相同。(许可仅有一个)

    @Test
    public void park2() {
        Thread currThread = Thread.currentThread();
        unsafe.unpark(currThread);
        unsafe.unpark(currThread);
        unsafe.unpark(currThread);

        unsafe.park(false, 0);
        unsafe.park(false, 0);
        System.out.println("execute success"); // 线程挂起,不会打印。
    }

LockSupport

整个JAVA并发框架中对线程的挂起/恢复操作被封装在 LockSupport 类中。

LockSupport 类中对于线程的挂起/恢复功能内部是通过unsafe park /unpark 方法实现。

线程对象锁

//获得对象锁
public native void monitorEnter(Object o);
//释放对象锁
public native void monitorExit(Object o);
//尝试获取对象锁,返回 true 或 false 表示是否获取成功
public native boolean tryMonitorEnter(Object o);

内存管理(非堆内存)

//分配内存
public native long allocateMemory(long bytes);
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);

//初始化内存内容
public native void setMemory(Object o, long offset, long bytes, byte value);
//初始化内存内容
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
}

//内存内容拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//内存内容拷贝
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
}

//获得本地指针
public native long getAddress(long address);
//存储本地指针到给定的内存地址
public native void putAddress(long address, long x);


//(boolean、byte、char、short、int、long、float、double) 都有以下 get、put 两个方法。 
//获得给定地址上的 int 值
public native int getInt(long address);
//设置给定地址上的 int 值
public native void putInt(long address, int x);

系统相关

//返回指针的大小。返回值为 4 或 8。
public native int addressSize();

//返回内存页的大小。
public native int pageSize();

内存屏障相关

//内存屏障,禁止 load 操作重排序,即屏障前的load操作不能被重排序到屏障后,屏障后的 load 操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止 store 操作重排序,即屏障前的 store 操作不能被重排序到屏障后,屏障后的 store 操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止 load、store 操作重排序
public native void fullFence();

volatile 相关读写

//从对象的指定偏移量处获取变量的引用,使用 volatile 的加载语义
//相当于 getObject(Object, long) 的 volatile 版本
//从主存中获取值
public native Object getObjectVolatile(Object o, long offset);
  
//存储变量的引用到对象的指定的偏移量处,使用 volatile 的存储语义
//相当于 putObject(Object, long, Object) 的 volatile 版本
//设置值刷新主存
public native void putObjectVolatile(Object o, long offset, Object x);
/**
 * Version of {@link #putObjectVolatile(Object, long, Object)}
 * that does not guarantee immediate visibility of the store to
 * other threads. This method is generally only useful if the
 * underlying field is a Java volatile (or if an array cell, one
 * that is otherwise only accessed using volatile accesses).
 */
public native void putOrderedObject(Object o, long offset, Object x);
  
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
public native void putOrderedInt(Object o, long offset, int x);
  
/** Ordered/Lazy version of {@link #putLongVolatile(Object, long, long)} */
public native void putOrderedLong(Object o, long offset, long x);

你可能感兴趣的:(J.U.C之Unsafe)