Unsafe -- Java的魔法类(二)

红烧土豆泥 等级 59 1 0
标签: 线程

Unsafe -- Java的魔法类(二)

原创不易,转载请注明来源

文接上集,上集主要是对Unsafe类做了简单的介绍,从本问开始,主要是对Unsafe类的八大类的详细用法展开演述。

1、CAS

  • java.util.concurrent.atomic相关类
  • Java AQS
  • ConcurrentHashMap

CAS 即compare and swap,中文名被翻译为比较并交换。在sun.misc.Unsafe类中,主要方法体现为一下三种:

public final boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)

public final boolean compareAndSwapInt(Object o, long offset, int expected, int x)

public final boolean compareAndSwapLong(Object o, long offset, long expected, long x)
  • 第一个参数o:当前的对象。
  • 第二个参数offset:内存地址。
  • 第三个参数expected:被交换的值。
  • 第四个参数x:交换的值。

解释:该方法的通过offset内存地址与被交换的值进行比较,如果他们相等,则将值替换为x,并返回true;

感觉听起来有点抽象的话,请看下面的案例:

案例: int a = 1; 先通过CAS的方式,将a的值变为2。

public class CasDemo {
    public static void main(String[] args) {
        CasDemo casDemo = new CasDemo();
        boolean flag = casDemo.compareAndSwap(1, 2);
        System.out.println(flag+", a="+casDemo.getA());
    }

    public volatile int a = 1;
    private long offset;

    public int getA() {
        return a;
    }

    /**
     *
     * @param curr  当前本应的值
     * @param expect 期望变为的值
     */
    @SneakyThrows
    public boolean compareAndSwap(int curr, int expect){
        Unsafe unsafe = getUnsafe();
        offset = unsafe.objectFieldOffset(this.getClass().getField("a")); //获取变量a的内存地址
        return unsafe.compareAndSwapInt(this,offset,curr,expect);
    }

    @SneakyThrows
    Unsafe getUnsafe() {
        Field declaredField = Unsafe.class.getDeclaredField("theUnsafe");
        declaredField.setAccessible(true);
        return (Unsafe) declaredField.get(null);
    }
}

运行结果:

Unsafe -- Java的魔法类(二)

有人会想,不就是换个值嘛,有必要搞得这么花里胡哨吗?

细想来,如果是单线程少并发情况下,确实没必要,直接进行赋值a++就好了;但如果是高并发情况呢?有人说可以加锁啊!直接使用synchronized关键字进行加锁,不可否认确实可以解决问题,但是直接上来使用一个重量级锁,不感觉屈才了吗?也确实没必要,细想来也有一个线程安全的类AtomicInteger可以完成此任务,直接调用其方法 getAndIncrement() 默认向上加一。点开看其源码,如下:

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();

public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
}

public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
}

public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {
        return compareAndSetInt(o, offset, expected, x);
}

原来也还是通过调用Unsafe类的CAS方法进行操作,但是考虑到在多线程情况下,假设现在共享变量a=1,有两个线程t1和t2去获取修改共享变量a,线程t1欲使共享a的值加1,线程t2也欲使共享a的值加1。t1跑的比较快,先获取到a的值为1,随后t2也获取到a的值为1,紧接着t1拿自己副本o(值为1)与真实的o(即a的值,值为1)比较,相等,修改成功,t2也拿自己副本o(值为1)与真实的o(即a的值,值为2)比较,不相等,修改失败;

案例:main线程和线程t1修改共享变量a,main线程先拿到值1,但并未写回,线程t1获取到假设为主线本应写回的数据1,欲写回数据2。

public class CasDemo03 {
    @SneakyThrows
    public static void main(String[] args) {
        CasDemo03 casDemo = new CasDemo03();

        new Thread(() -> {
            System.out.println("等待。。。");
            int andSwap = casDemo.compareAndSwap(1, 2);
            System.out.println(andSwap);
        }).start();

        Thread.sleep(3000);
        casDemo.setA(1);
    }

    public volatile int a = 0;
    private long offset;
    private Unsafe unsafe = getUnsafe();

    public Integer getA() {
        return a;
    }

    @SneakyThrows
    public void setA(Integer a) {
        offset = unsafe.objectFieldOffset(this.getClass().getField("a"));
        this.a = a;
    }

    //获取变量a的内存地址
    @SneakyThrows
    public long getOffset() {
        offset = unsafe.objectFieldOffset(this.getClass().getField("a"));
        return offset;
    }

    @SneakyThrows
    public int compareAndSwap(Integer curr, Integer expect) {
        offset = getOffset();
        do {
        } while (!unsafe.compareAndSwapInt(this, offset, curr, expect));//如果判断失败,则自旋等待
        return getA();
    }

    @SneakyThrows
    private final static Unsafe getUnsafe() {
        Field declaredField = Unsafe.class.getDeclaredField("theUnsafe");
        declaredField.setAccessible(true);
        return (Unsafe) declaredField.get(null);
    }
}

可以很容易的发现线程t1会先打印出等待。。。 ,直到main线程修改共享变量为1之后才继续运行。其实在此处,大家可能也很容易就发现一个问题,假使在t1循环判断期间,有一个线程偷偷将共享变量a变为其他值,然后再偷偷变回1,线程t1是绝对发现不了的,这也就是我们常说的多线程的ABA问题。解决ABA主流就是引入版本号(这也是jdk源码中解决ABA问题的方式),从Java1.5 开始,JDK 的 atomic 包里提供了一个类 AtomicStampedReference 用来解决 ABA 问题。详细请看AtomicStampedReference源码中的compareAndSet()方法。

收藏
评论区

相关推荐

AtomicStampedReference是怎样解决CAS的ABA问题
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。 什么是ABA问题但凡对Java有一点深入就会知道 CAS,即 compareAndSwap。在Java中使用 Unsafe 类提供的native方法可以直接操作内存,其中就有对compareAndSwap的实现。javapublic final nati
CAS之基本类型与引用类型
CAS之基本类型与引用类型 ============= 1.1 long -------- static final Unsafe unsafe ; static Long offset; private volatile long state= 1; static { try {
J.U.C并发包诞生的那些事儿
前言 == J.U.C是java包java.util.concurrent的简写,中文简称并发包,是jdk1.5新增用来编写并发相关的基础api。java从事者一定不陌生,同时,流量时代的今天,并发包也成为了高级开发面试时必问的一块内容,本篇内容主要聊聊J.U.C背后的哪些事儿,然后结合LockSupport和Unsafe探秘下并发包更底层的哪些代码,有可
Java Unsafe 类
Unsafe类是啥? ---------- Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是丝毫不建议在生产环境中使用这
Java 学习笔记 一
一、数据库准备和Java环境配置 ---------------- ### (一)安装MySQL、Navicat、JDK、Eclipse ### (二)配置Java环境变量 ### (三)导入 jar(mysql-connection-java -> Build Path) 二、SUN标准规范 --------- ### (一)加载驱动
Java中的基本数据类型和引用数据类型的区别
一、前言 ==== 众所周知`Java`是一种强类型语言,在`Java`语言中,`Java`的数据类型一共分为两大类,分别为`基本数据类型`和`引用数据类型`,其中基本数据类型细分小类可分为整数类型、浮点类型、字符类型、布尔类型这四小类。 二、基本数据类型和引用数据类型 =============== 1\. 基本数据类型 ---------- 只有
Java基础学习总结(7)——Object类
一、Object类介绍 -----------   Object类在JAVA里面是一个比较特殊的类,JAVA只支持单继承,子类只能从一个父类来继承,如果父类又是从另外一个父类继承过来,那他也只能有一个父类,父类再有父类,那也只能有一个,JAVA为了组织这个类组织得比较方便,它提供了一个最根上的类,相当于所有的类都是从这个类继承,这个类就叫Object。所以
Java基础(二)数据类型
  数据类型主要分为基本类型和引用类型两大类。   一、基本类型   1.基本类型又分为数值类型和boolean类型,   (1)数值类型包括浮点数类型、整数类型和字符类型   整型                                                 浮点型(初始化时需要加f或d)  字符类型   byte       
Java文件格式
\*.java文件是保存源代码的文本文件 (\*代表类名) 使用 javac \*.java可以编译该文件 使用 java \*可以运行该类 \*.class是用于保存 Java类的 二进制编码以及Class对象,每一个 Java类都有一个解释该类特征的 Class对象。 \*.jar文件 是一种压缩文件格式 打包命令 jar cvf
Java锁事之Unsafe、CAS、AQS知识点总结
![](https://oscimg.oschina.net/oscnet/e14679ca0bdd2b95aa5869200e6bbaefff2.gif) 关注 “Java艺术”一起来充电吧! Unsafe、CAS、AQS是我们了解Java中除synchronized之外的锁必须要掌握的重要知识点。CAS是一个比较和替换的原子操作,AQS的实现
java 数据结构(十二):Collections工具类的使用
Collections工具类 1.作用:操作Collection和Map的工具类 2.常用方法: reverse(List):反转 List 中元素的顺序 shuffle(List):对 List 集合元素进行随机排序 sort(List):根据元素的自然顺序对指定 List 集合元素升序排序 sort(List,Comparator)
Unsafe -- Java的魔法类(一)
Unsafe Java的魔法类(一)::: tip 原创不易,转载请注明来源::: 一、简介​ Unsafe,顾名思义,不安全的;Unsafe类位于sun.misc包下,执行低级、不安全操作的方法集合。对于号称是安全的编程语言Java来说,无疑它是个例外,不仅仅是因为它可以堆内存进行操作,还是通过非常规化手段获取到对象,还是对线程调度毛手毛脚,总是它不属
DOM解析XML案例Demo(一)
### **一、Java中操作DOM中常用的类** * Node  数据类型基类 * Element  最常用的类 * Attr  Element的属性 * Text  Element or Attr的内容 * Document  代表整个XML文档,代表DOM tree #
JVM(Java SE 11版本)加载类和接口
本文介绍了Java虚拟机(Java SE 11版本)加载类和接口。 加载类和接口 ------ 加载是指查找具有特定名称的类或接口类型的二进制形式的过程。典型的做法是,查找事先由Java编译器从源代码计算而来二进制表示,但也可能是通过动态计算。 二进制形式最终会构造成一个Class对象。 加载的精确语义在Java Java Machine Specif
Unsafe -- Java的魔法类(二)
Unsafe Java的魔法类(二) 原创不易,转载请注明来源文接上集,上集主要是对Unsafe类做了简单的介绍,从本问开始,主要是对Unsafe类的八大类的详细用法展开演述。 1、CAS java.util.concurrent.atomic相关类 Java AQS ConcurrentHashMapCAS 即compare and swap,中文名被翻