文档章节

对Unsafe类的理解

码子
 码子
发布于 2017/07/28 17:25
字数 1166
阅读 26
收藏 0

最近在研究并发包Abstractqueuedsynchronizer的源码,那自然就绕不开Unsafe类,于是就对Unsafe简单地总结了一下。在初步了解了Unsafe的用法后,觉得简直就一黑科技。
一、获取Unsafe对象
由于unsafe各种黑科技,要保证不会被误用,所以Unsafe.getUnsafe()中加了个限制:

public static Unsafe getUnsafe() {
            //拿到调用该方法的类
                Class var0 = Reflection.getCallerClass();
                //判断该类加载器是不是BootstrapClassLoader,是的话则getClassLoader()返回null
                if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
                    throw new SecurityException("Unsafe");
                } else {
                    return theUnsafe;
                }
            }


当调用该方法的类的不是系统指定类,即调用类的类加载器不是BootstrapClassLoader时会抛异常,所以不能像AQS一样简单获取。我们可以用反射来获取这个类的实体。

public class UnsafeGenerator {
    public static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

 二、UnSafe操作对象示例
然后我们可以做很多违反java伦理道德的事情

1. 任意修改对象的属性

在aqs中会提前获取stateOffset等long类型的值,这些值是该属性的内存偏移量。比如我这个对象分配了一段内存,那这个值就是在相对于该内存的偏移量就是该属性的位置。
而这个偏移量对于这个类是不变的。通过对象的实例及偏移量我就可以获得这个属性,从而任意修改其值。
示例:

public class ObjectOffsetTest {
    public static void main(String[] args) throws Exception {
        Unsafe unsafe = UnsafeGenerator.getUnsafe();
        Person person = new Person();
    //拿到name的属性的内存偏移量
        long nameOffset = unsafe.objectFieldOffset(Person.class.getDeclaredField("name"));
        unsafe.putObject(person, nameOffset, "hello world");
        System.out.println(person.getName());
    }
}
class Person {
    private String name;
    public String getName() {
        return name;
    }
}


输出:hello world

2. 修改数组的值

数组由于没有field,所以操作方式稍有区别,但整理思想还是一样,就是获取指定元素在内存中的位置,修改。

public class ArrayOffsetTest {
    public static void main(String[] args) throws Exception {
        Unsafe unsafe = UnsafeGenerator.getUnsafe();
        int[] arr = {1, 2, 3};
    //数组对象起始地址的内存偏移量
        long arrayOffset = unsafe.arrayBaseOffset(arr.getClass());
    //每个数组元素占用多少字节
        long scale = unsafe.arrayIndexScale(arr.getClass());
        unsafe.putInt(arr, arrayOffset + scale * 1, 1000);
        System.out.println(Arrays.toString(arr));
    }
}


输出:[1, 1000, 3]

3. 绕开单例的限制

public class SingleTonTest {
    public static void main(String[] args) throws Exception {
        Unsafe unsafe = UnsafeGenerator.getUnsafe();
        SingleTon singleTon = (SingleTon) unsafe.allocateInstance(SingleTon.class);
        System.out.println(singleTon == SingleTon.getSingleton());
    }
}
class SingleTon {
    private static SingleTon singleton = new SingleTon();
    private SingleTon() {}
    public static SingleTon getSingleton() {
        return singleton;
    }
}


输出:false
 三、CAS原子操作
既然可以直接操作内存中的对象,没有了缓存这一层,很多并发问题就解决了。我只需要很简单的判断内存中的某个值跟我期望的旧值是否一致,
一致的话我就把他修改为期望的新值。如果不一致,则说明我拿到了旧值到我执行值修改期间有人捷足先登改掉了该内存位置的值,那就不进行
该操作。这样就实现了原子操作:从get到set是原子的,具体实现就是各种compareAndSet方法。
既然有了原子操作的支持,我通过一个volatile成员变量,通过对该变量值的判断,就可以判断是否有线程占用了该对象,比如aqs中的state,
继而可以很灵活地根据自己需要,实现类似CountDownLatch,ReentrantLock自己的工具类、锁。
思想跟之前直接操作对象类似,就是多加了个预期的oldvalue参数。

//object:需要被修改类的实例
//fieldOffset:该field的内存偏移量,unsafe.objectFieldOffset(Field field)
//oldValue:旧的预期值
//newValue:新的值
public final native boolean compareAndSwapInt(Object object, long fieldOffset, int oldValue, int newValue)


静态成员变量的操作跟非静态略有区别:

public class StaticFiledTest {
    private static int staticoffset = 20;
    public static void main(String[] args) throws Exception {
        Unsafe unsafe = UnsafeGenerator.getUnsafe();
        Field field = UnsafeTest.class.getDeclaredField("staticoffset");
        long offset = unsafe.staticFieldOffset(field);
        Object staticFieldBase = unsafe.staticFieldBase(field);
        int val = unsafe.getInt(staticFieldBase, offset);
        System.out.println(val);
    }
}


输出:20
 四、park/unpark
在aqs中通过LockSupport来调用unsafe的park和unpark来实现对线程的挂起和唤醒。

//唤醒
public native void unpark(Thread thread);
//isAbsolute
//为true时,表示绝对时间,单位是毫秒   time = System.currentTimeMillis() + 3000  表示当前时间3秒后
//为false时,表示相对时间,单位是纳秒   time = 30000000,00挂起一段时间
//park(false, 0)表示一直等待,直到有unpark(thisThread)
public native void park(boolean isAbsolute, long time);


park/unpark比wait(),notify()灵活很多,因为这两者的时序可以先unpark,给个唤醒的许可,当执行到park时会自动执行下去,而notify()在wait()之前则会导致死锁,而且unpark(thread)可以精确的唤醒某个线程,很灵活。
 

public class UnparkTest {
    public static void main(String[] args) {
        Unsafe unsafe = UnsafeGenerator.getUnsafe();
        Thread thread = Thread.currentThread();
        unsafe.unpark(thread);
        System.out.println("before park");
        unsafe.park(false, 0);
        System.out.println("after park");
    }
}

 

© 著作权归作者所有

码子
粉丝 0
博文 1
码字总数 1166
作品 0
松江
私信 提问
Unsafe API介绍及其使用

废话   个人理解:java 出现的原因之一,就是对内存的管理;在c/c++,内存可以随心使用,超高的性能也伴有极高的风险;java极大的规避了这种风险,却也降低了程序运行的性能;那么java是否...

大雨如注
2018/08/13
0
0
Java并发编程-无锁CAS与Unsafe类及其并发包Atomic

出自【zejian的博客】 在前面一篇博文中,我们曾经详谈过有锁并发的典型代表synchronized关键字,通过该关键字可以控制并发执行过程中有且只有一个线程可以访问共享资源,其原理是通过当前线...

芝麻绿豆
2017/08/07
613
0
Java并发编程之Unsafe类和LockSupport类源码分析

一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库。 rt.jar 中 Unsafe 类主要函数讲解,...

狂小白
2018/06/06
0
0
Java并发学习(六)-深入分析CAS操作

What is CAS 在Java并发包下源码中,经常会遇到CAS操作,即Compare And Swap操作,例如:

anLA_
2017/11/27
0
0
Java Concurrency(二)——J.U.C atomic包源码解读

java5之后的java.util.concurrent包是世界级并发大师Doug Lea的作品,里面主要实现了 atomic包里Integer/Long对应的原子类,主要基于CAS; 一些同步子,包括Lock,CountDownLatch,Semaphore...

谦谦君子
2015/01/06
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
今天
5
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
今天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
今天
4
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
今天
7
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部