Java Review - 并发编程_Unsafe

简介: Java Review - 并发编程_Unsafe

195d03d17afc4a928bc581f313b01dfe.png

Unsafe


JDK 的 rt.jar 包中的 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用 JNI 的方式访问本地 C++ 实现库。


Unsafe 提供的几个主要的方法


下面我们来了解一下 Unsafe 提供的几个主要的方法以及编程时如何使用 Unsafe 类做一些事情。


long objectFieldOffset(Field field)


返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该 Unsafe 函数中访问指定字段时使用。

如下代码使用 Unsafe 类获取变量 value 在 AtomicLong 对象中的内存偏移


d09342f934ce444888e90649ad723b86.png


int arrayBaseOffset(Class arrayClass)

获取数组中第一个元素的地址


int arrayIndexScale(Class arrayClass)

获取数组中一个元素占用的字节


boolean compareAndSwapLong(Object obj, long offset, long expect, long update)


比较对象obj中偏移量为offset的变量的值是否与expect相等,相等则使用update值更新,然后返回true,否则返回false


public native long getLongvolatile(Object obj, long offset)

获取对象obj中偏移量为offset的变量对应volatile语义的值


void putLongvolatile(Object obj, long offset, long value)

设置obj对象中offset偏移的类型为long的field的值为value,支持volatile语义


void putOrderedLong(Object obj, long offset, long value)


设置obj对象中offset偏移地址对应的long型field的值为value。这是一个有延迟的putLongvolatile方法,并且不保证值修改对其他线程立刻可见。只有在变量使用volatile修饰并且预计会被意外修改时才使用该方法。


void park(boolean isAbsolute, long time)


阻塞当前线程,其中参数isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞线程会被唤醒,这个time是个相对值,是个增量值,也就是相对当前时间累加time后当前线程就会被唤醒。


如果isAbsolute等于true,并且time大于0,则表示阻塞的线程到指定的时间点后会被唤醒,这里time是个绝对时间,是将某个时间点换算为ms后的值。


另外,当其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时,当前线程也会返回,而当其他线程调用了unPark方法并且把当前线程作为参数时当前线程也会返回。


void unpark(Object thread)


唤醒调用park后阻塞的线程


下面是JDK8新增的函数,这里只列出Long类型操作。


long getAndSetLong(Object obj, long offset, long update)


获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量volatile语义的值为update


5289040097cf4cd6adcff2e338f3d6e7.png

由以上代码可知,首先(1)处的getLongvolatile获取当前变量的值,然后使用CAS原子操作设置新值。这里使用while循环是考虑到,在多个线程同时调用的情况下CAS失败时需要重试。


long getAndAddLong(Object obj, long offset, long addValue)


获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量值为原始值+addValue


ebcd9416660046cd8b942588825ed3d1.png

类似getAndSetLong的实现,只是这里进行CAS操作时使用了原始值+传递的增量参数addValue的值。


使用Unsafe类

Unsafe 这个类如此厉害,试一试???

import sun.misc.Unsafe;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/28 20:05
 * @mark: show me the code , change the world
 */
public class TestUnSafe {
    // 1 获取 Unsafe的实例
    static final Unsafe unsafe = Unsafe.getUnsafe();
    // 2 记录变量 state在类 Testunsafe中的偏移值
    static final long stateoffset;
    // 3 变量
    private volatile long state = 0;
    static {
        try {
            // 4 获取state变量在类TestUnsafe中的偏移量
            stateoffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            throw new Error(e);
        }
    }
    public static void main(String[] args) {
        // 5. 创建实例
        TestUnSafe testUnSafe = new TestUnSafe();
        // 6 设置state值为1 通过cas算法
        boolean b = unsafe.compareAndSwapLong(testUnSafe, stateoffset, 0, 1);
        System.out.println(b);
    }
}


代码1 获取了Unsafe的一个实例


代码3创建了一个变量state并初始化为0。


代码4使用unsafe.objectFieldOffset获取TestUnSafe类里面的state变量,在TestUnSafe对象里面的内存偏移量地址并将其保存到stateOffset变量中。


代码6 调用创建的unsafe实例的compareAndSwapInt方法,设置test对象的state变量的值。具体意思是,如果test对象中内存偏移量为stateOffset的state变量的值为0,则更新该值为1。


运行上面的代码,我们期望输出true,然而执行后会输出如下结果


aa9926084b354398aa3d5c5c89a9220c.png


    @CallerSensitive
    public static Unsafe getUnsafe() {
        // 7 
        Class var0 = Reflection.getCallerClass();
    // 8 
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }


继续看下

    //  9 判断是不是BootStrap类加载器加载的 
    public static boolean isSystemDomainLoader(ClassLoader var0) {
        return var0 == null;
    }

代码7获取调用getUnsafe这个方法的对象的Class对象,这里是TestUnSafe.class。


代码8判断是不是Bootstrap类加载器加载的localClass,在这里是看是不是Bootstrap加载器加载了TestUnSafe.class。很明显由于TestUnSafe.class是使用AppClassLoader加载的,所以这里直接抛出了异常。


思考一下,这里为何要有这个判断? 我们知道Unsafe类是rt.jar包提供的,rt.jar包里面的类是使用Bootstrap类加载器加载的,而我们的启动main函数所在的类是使用AppClassLoader加载的,所以在main函数里面加载Unsafe类时,根据委托机制,会委托给Bootstrap去加载Unsafe类。


如果没有代码8的限制,那么我们的应用程序就可以随意使用Unsafe做事情了,而Unsafe类可以直接操作内存,这是不安全的,所以JDK开发组特意做了这个限制,不让开发人员在正规渠道使用Unsafe类,而是在rt.jar包里面的核心类中使用Unsafe功能。


如果开发人员真的想要实例化Unsafe类,那该如何做?


方法有多种,既然从正规渠道访问不了,那么就玩点黑科技,使用万能的反射来获取Unsafe实例方法。

import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/28 20:05
 * @mark: show me the code , change the world
 */
public class TestUnSafe {
    // 1 获取 Unsafe的实例
    static final Unsafe unsafe  ;
    // 2 记录变量 state在类 Testunsafe中的偏移值
    static final long stateoffset;
    // 3 变量
    private volatile long state = 0;
    public long getState() {
        return state;
    }
    static {
        try {
           // 使用反射获取Unsafe成员变量theUnsafe
            Field field =  Unsafe.class.getDeclaredField("theUnsafe");
            // 设置为可存取
            field.setAccessible(true);
            // 获取该变量的值
            unsafe  = (Unsafe)field.get(null);
            // 获取state在TestUnSfate中的偏移量
            stateoffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            throw new Error(e);
        }
    }
    public static void main(String[] args) {
        // 5. 创建实例
        TestUnSafe testUnSafe = new TestUnSafe();
        System.out.println("修改前:" + testUnSafe.getState());
        // 6 设置state值为1 通过cas算法
        boolean b = unsafe.compareAndSwapLong(testUnSafe, stateoffset, 0, 1);
        System.out.println("修改  " + b);
        System.out.println("修改前:" + testUnSafe.getState());
    }
}


在如上代码中,通过反射获取unsafe的实例, 运行后输出结果如下。


af05be883bdd4590beacea160ffabdaf.png


相关文章
|
12月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
161 0
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
839 6
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
387 1
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
188 2
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
Java 开发者
深入探索Java中的并发编程
本文将带你领略Java并发编程的奥秘,揭示其背后的原理与实践。通过深入浅出的解释和实例,我们将探讨Java内存模型、线程间通信以及常见并发工具的使用方法。无论是初学者还是有一定经验的开发者,都能从中获得启发和实用的技巧。让我们一起开启这场并发编程的奇妙之旅吧!
117 5
|
算法 安全 Java
Java中的并发编程是如何实现的?
Java中的并发编程是通过多线程机制实现的。Java提供了多种工具和框架来支持并发编程。
98 1
|
XML 存储 Java
Java Review(三十三、异常处理----补充:断言、日志、调试)
Java Review(三十三、异常处理----补充:断言、日志、调试)
254 0
|
机器学习/深度学习 Java 程序员
Java Review(三十二、异常处理)
Java Review(三十二、异常处理)
189 0
Java Review(三十二、异常处理)
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
165 1