zoukankan      html  css  js  c++  java
  • Java并发编程基础-Unsafe

    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 object
        Field 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),示例如下。


           
          String[] strings = new String[]{"1","2","3"};
          long i = unsafe.arrayBaseOffset(String[].class);
          System.out.println("string[] base offset is :"+i);
          //every index scale
          long 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 string
          unsafe.putObject(strings,i+scale*0,"100");
          //print first string in strings[] again
          System.out.println("after set ,first element is :"+unsafe.getObject(strings, i+scale*0));
        2. 对象操作
          可以通过类的class对象创建类对象(allocateInstance),获取对象属性的偏移量(objectFieldOffset),通过偏移量设置对象的值(putObject),示例如下。


           
          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,用于初始化),在指定的内存位置中设置值(putIntputBooleanputDouble等基本类型),示例如下。
          //分配一个8byte的内存
          long address = unsafe.allocateMemory(8L);
          //初始化内存填充0
          unsafe.setMemory(address,8L,(byte) 0);
          //测试输出
          System.out.println("add byte to memory:"+unsafe.getInt(address));
          //设置0-3 4个byte为0x7fffffff
          unsafe.putInt(address,0x7fffffff);
          //设置4-7 4个byte为0x80000000
          unsafe.putInt(address+4,0x80000000);
          //int占用4byte
          System.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_OFFSETUnsafe.ARRAY_BOOLEAN_BASE_OFFSET等)、基本类型数组内元素的间隔(Unsafe.ARRAY_INT_INDEX_SCALEUnsafe.ARRAY_BOOLEAN_INDEX_SCALE等),示例如下。
           
          //get os address size
          System.out.println("address size is :"+unsafe.addressSize());
          //get os page size
          System.out.println("page size is :"+unsafe.pageSize());
          //int array base offset
          System.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位

  • 相关阅读:
    android代码签名和混乱的包装
    C# 中对WinForm窗体中的控件快速设置TableIndex次序
    常用的Oracle数据库语句 (待更新完毕)
    [转] C# 键盘中的按键对应的KeyValue
    Oracle 数据库中日期时间的插入操作
    C#操作Excel,对Sheet插入次序的控制 (有待完善)
    Simditor图片上传
    文件类似的推理 -- 超级本征值(super feature)
    leetcode 名单 Insertion Sort List
    汉高澳大利亚sinox2014电影播放flash最好的办法是安装游戏windows文本firefox
  • 原文地址:https://www.cnblogs.com/shoshana-kong/p/10834477.html
Copyright © 2011-2022 走看看