博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java并发编程基础-Unsafe
阅读量:7045 次
发布时间:2019-06-28

本文共 5504 字,大约阅读时间需要 18 分钟。

hot3.png

  1. 前言:
    Unsafe是Java中一个底层类,包含了很多基础的操作,比如数组操作、对象操作、内存操作、CAS操作、线程(park)操作、栅栏(Fence)操作,JUC包、一些三方框架都使用Unsafe类来保证并发安全。
  2. 介绍:
    1. 获取Unsafe对象
      Unsafe构造方法为私有,虽然提供了一个getUnsafe静态方法,但会判断加载这个类的加载器是否为null,即判断加载器是否为Bootstrap ClassLoader。用户创建的类默认都是由App ClassLoader进行加载,因此自己编写的代码不使用特殊方式是无法获取到Unsafe实例,观察源码得知,可以使用反射获取theUnsafe属性,从而获得实例,代码示例如下:
       
      Unsafe unsafe = null;//get unsafe objectField getUnsafe = null;try {    getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");    getUnsafe.setAccessible(true);    unsafe = (Unsafe) getUnsafe.get(null);} catch (NoSuchFieldException e) {    e.printStackTrace();}
    2. 基本操作
      1. 数组操作
        可以获取数组的在内容中的基本偏移量(arrayBaseOffset),获取数组内元素的间隔(比例),根据数组对象和偏移量获取元素值(getObject),设置数组元素值(putObject),示例如下。
        b50a1d892db12ed03d42a6cd65550565856.jpg
         
        String[] strings = new String[]{"1","2","3"};long i = unsafe.arrayBaseOffset(String[].class);System.out.println("string[] base offset is :"+i);//every index scalelong scale = unsafe.arrayIndexScale(String[].class);System.out.println("string[] index scale is "+scale);//print first string in strings[]System.out.println("first element is :"+unsafe.getObject(strings, i));//set 100 to first stringunsafe.putObject(strings,i+scale*0,"100");//print first string in strings[] againSystem.out.println("after set ,first element is :"+unsafe.getObject(strings, i+scale*0));
      2. 对象操作
        可以通过类的class对象创建类对象(allocateInstance),获取对象属性的偏移量(objectFieldOffset),通过偏移量设置对象的值(putObject),示例如下。
        c7a7633d211385a104a94b4678ff19b8f41.jpg
         
        public class User {    private String username;    private int age;    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }}
        try {    User user = (User) unsafe.allocateInstance(User.class);    //simple set username    user.setUsername("123");    System.out.println("simple set,allocate Intstance user name:"+user.getUsername());    Field usernameField = User.class.getDeclaredField("username");    long objectFieldOffset = unsafe.objectFieldOffset(usernameField);    //use unsafe set    unsafe.putObject(user,objectFieldOffset,"456");    System.out.println("use unsafe,allocate Intstance user name:"+user.getUsername());} catch (InstantiationException e) {    e.printStackTrace();} catch (NoSuchFieldException e) {    e.printStackTrace();}
      3. 内存操作
        可以在Java内存区域中分配内存(allocateMemory),设置内存(setMemory,用于初始化),在指定的内存位置中设置值(putInt\putBoolean\putDouble等基本类型),示例如下。
        //分配一个8byte的内存long address = unsafe.allocateMemory(8L);//初始化内存填充0unsafe.setMemory(address,8L,(byte) 0);//测试输出System.out.println("add byte to memory:"+unsafe.getInt(address));//设置0-3 4个byte为0x7fffffffunsafe.putInt(address,0x7fffffff);//设置4-7 4个byte为0x80000000unsafe.putInt(address+4,0x80000000);//int占用4byteSystem.out.println("add byte to memory:"+unsafe.getInt(address));System.out.println("add byte to memory:"+unsafe.getInt(address+4));
      4. 常量获取
        可以获取地址大小(addressSize),页大小(pageSize),基本类型数组的偏移量(Unsafe.ARRAY_INT_BASE_OFFSET\Unsafe.ARRAY_BOOLEAN_BASE_OFFSET等)、基本类型数组内元素的间隔(Unsafe.ARRAY_INT_INDEX_SCALE\Unsafe.ARRAY_BOOLEAN_INDEX_SCALE等),示例如下。
         
        //get os address sizeSystem.out.println("address size is :"+unsafe.addressSize());//get os page sizeSystem.out.println("page size is :"+unsafe.pageSize());//int array base offsetSystem.out.println("unsafe array int base offset:"+Unsafe.ARRAY_INT_BASE_OFFSET);
      5. 线程许可
        许可线程通过(park),或者让线程等待许可(unpark),示例如下。
        Thread parkThread = new Thread(new Runnable() {    @Override    public void run() {        long startTime = System.currentTimeMillis();        //纳秒,相对时间park        unsafe.park(false,3000000000L);        //毫秒,绝对时间park        //unsafe.park(true,System.currentTimeMillis()+3000);        System.out.println("main thread end,cost :"+(System.currentTimeMillis()-startTime)+"ms");    }});parkThread.start();try {    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {    e.printStackTrace();}//注释掉下一行后,线程3秒数后进行输出,否则在1秒后输出unsafe.unpark(parkThread);
      6. CAS操作
        Compare And Swap(比较并交换),当需要改变的值为期望的值时,那么就替换它为新的值,是原子(不可在分割)的操作。很多并发框架底层都用到了CAS操作,CAS操作优势是无锁,可以减少线程切换耗费的时间,但CAS经常失败运行容易引起性能问题,也存在ABA问题。在Unsafe中包含compareAndSwapObject、compareAndSwapInt、compareAndSwapLong三个方法,compareAndSwapInt的简单示例如下。
        //参考 介绍-基本操作-对象操作中的User定义User user = new User();user.setAge(1);try {    Field ageField = user.getClass().getDeclaredField("age");    long l = unsafe.objectFieldOffset(ageField);    ageField.setAccessible(true);    //比较并交换,比如age的值如果是所期望的值1,那么就替换为2,否则不做处理    unsafe.compareAndSwapInt(user,l,1,2);    System.out.println("user age is :" + user.getAge());} catch (NoSuchFieldException e) {    e.printStackTrace();}
      7. 内存栅栏
        用于防止指令重排序,包含fullFence,loadFence,StoreFence三个方法。现代的CPU运行速度很快,很多指令重排序的例子已经无法得到想要的效果,因此借用stackoverflow上的一段伪代码示例。
         
        // CPU 0:void shutDownWithFailure(void){  failure = 1; // must use SOB as this is owned by CPU 1  SFENCE // next instruction will execute after all SOBs are processed  shutdown = 1; // can execute immediately as it is owned be CPU 0}// CPU1:void workLoop(void){  while (shutdown == 0) { ... }  LFENCE // next instruction will execute after all LOBs are processed  if (failure) { ...}}

PS:

  • 在Java新版本开发的过程中,曾经传出Oracle要移除掉Unsafe类,引起了很大的恐慌,但在Java9发布时,发现jdk.internal.misc包路径出现了Unsafe类,不仅开放使用而且还增加了大量的注释方便理解,说明Java在开源的道路上依然在前进。
  • 对于getXXVolatile的理解:如果一个Java变量被Volatile修饰,使用此方法则可以获得内存内最新的值,而不是线程缓存;如果普通的Java变量使用此方法,那么和使用getXX()方法效果一样,可能获取到的事线程缓存。

参考书籍及网址:

  • 《深入理解Java虚拟机》
  •   https://stackoverflow.com/questions/23603304/java-8-unsafe-xxxfence-instructions

PS:研究基于MAC+Idea+JDK1.8 64位

Keep Calm and Carry on!

转载于:https://my.oschina.net/gordonfor/blog/1922683

你可能感兴趣的文章
Zombie.js Insanely fast, headless full-stack testing using Node.js
查看>>
SpringMVC源码分析3:DispatcherServlet的初始化与请求转发
查看>>
Hyper-V故障转移群集搭建(3)
查看>>
VMware下ubuntu上网设置(二)
查看>>
sqlplus的session下无法使用退格键的问题处理
查看>>
Centos7下部署本地的gitlab(CE版本)
查看>>
docker配置桥接网络
查看>>
PHP使用file_get_contents函数POST数据
查看>>
mariadb安装
查看>>
MySQL基础day07_mysql集群实例-MySQL 5.6
查看>>
高性能 Web 缓存服务器 nuster 1.7.9.5 发布
查看>>
nginx对后端的目录进行反向代理
查看>>
NAT(Cisco)
查看>>
HP LaserJet Pro P1106网络打印机64位驱动安装
查看>>
SpringMVC+MyBatis整合(1)generator篇
查看>>
XMPP getting "Not Authorized" when joining an P/W protected, already open chat room
查看>>
C#设计模式之总结篇
查看>>
基于Sbo 2005B的富盛企业经营分析插件共享版免费下载
查看>>
手机上的搜索引擎-Windows Live Search Mobile 发布!
查看>>
五元组和防火墙
查看>>