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,用于初始化),在指定的内存位置中设置值(putInt\putBoolean\putDouble等基本类型),示例如下。
        //分配一个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_OFFSET\Unsafe.ARRAY_BOOLEAN_BASE_OFFSET等)、基本类型数组内元素的间隔(Unsafe.ARRAY_INT_INDEX_SCALE\Unsafe.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位

Java并发编程基础-Unsafe的更多相关文章

  1. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  2. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  3. Java并发编程--基础进阶高级(完结)

    Java并发编程--基础进阶高级完整笔记. 这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记. 参考链接:https:// ...

  4. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  5. java并发编程基础概念

    本次内容主要讲进程和线程.CPU核心数和线程数.CPU时间片轮转机制.上下文切换,并行和并发的基本概念以及并发编程的好处和注意事项,为java并发编程打下扎实基础. 1.什么是进程和线程 1.1 进程 ...

  6. 多线程(一)java并发编程基础知识

    线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...

  7. Java并发编程基础三板斧之Semaphore

    引言 最近可以进行个税申报了,还没有申报的同学可以赶紧去试试哦.不过我反正是从上午到下午一直都没有成功的进行申报,一进行申报 就返回"当前访问人数过多,请稍后再试".为什么有些人就 ...

  8. Java并发编程基础之volatile

    首先简单介绍一下volatile的应用,volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...

  9. Java并发编程基础-ReentrantLock的机制

    同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...

随机推荐

  1. vue项目进入mui.js报错 typeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode .....

    在做项目时,引用mui报错如下 看样子就是不支持严格模式吧. 解决方法是在根目录 下的.babelrc文件里面添加过滤掉.不用严格模式

  2. Spring框架快速入门

    ssm框架是目前最流行的框架之一.他包括Spring,springMVC,mybatis.今天来简单介绍一下spring. 1.spring是什么? 答:spring是一个轻量级的框架. 2.两大核心 ...

  3. PHP redis 常用操作

    //在列表头部插入一个值one,当列表不存在时自动创建一个列表,key1为列表名 $redis->lpush("key1", "one"); //在列表尾 ...

  4. C语言形参跟实参详解

    形参与实参今天我们来说下c语言形参与实参的区别,形参跟实参理解的话也很简单,但是好多同学关于这个知识点都是一知半解,没有真正的去透彻,一问都知道,但在真正引用的时候还会出现很多问题,而百度的时候又会说 ...

  5. Python3+HTMLTestRunner+SMTP生成测试报告后发送邮件

    在前一篇https://www.cnblogs.com/zhengyihan1216/p/11549820.html 中记录了如何生成html格式的报告, 这篇记录下怎么将测试报告通过邮件发出 1.对 ...

  6. LAMP架构性能测试+php优化

     性能测试:1. 首先查看一下服务器的硬件性能free  -m  ---->查看一下内存的情况lscpu或者cat  /proc/cpuinfo     ------à查看一下cpu的情况2.  ...

  7. 判断cms-暴库-后台查找-密码破解

    1.判断网站CMS类型 判断目标 使用工具: 脚本语言 wvs 御剑     domain明小子 操作系统 wwwscan 啊d pangolin(穿山甲) 搭建平台 站长工具 netpark     ...

  8. vue中axios的简单使用

    我们一般在用jq的时候会使用到ajax来进行与服务器之间的交流,vue中也提供了相应的类似于ajax的方法-axios来进行与服务器之间的数据传递 现在的这篇是最简单的使用,后续会添加上来复杂的使用 ...

  9. CEfSharp下载文件 弹出保存框,实现 IDownloadHandler 接口

    上节讲了如何将CefSharp集成到C#中,但集成后将web界面链接进ChromiumWebBrowser后,但web界面上下载附件的功能不好使咯. 百度了半天还是没搞定,只能去看官网的Excampl ...

  10. 20180610模拟赛T1——脱离地牢

    Description 在一个神秘的国度里,年轻的王子Paris与美丽的公主Helen在一起过着幸福的生活.他们都随身带有一块带磁性的阴阳魔法石,身居地狱的魔王Satan早就想着得到这两块石头了,只要 ...