Unsafe -- Java的魔法类(二)

红烧土豆泥
• 阅读 650

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()方法。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
捉虫大师 捉虫大师
2年前
AtomicStampedReference是怎样解决CAS的ABA问题
本文已收录https://github.com/lkxiaolou/lkxiaolou欢迎star。什么是ABA问题但凡对Java有一点深入就会知道CAS,即compareAndSwap。在Java中使用Unsafe类提供的native方法可以直接操作内存,其中就有对compareAndSwap的实现。javapublicfinalnati
Wesley13 Wesley13
2年前
java常用类(2)
三、时间处理相关类Date类:计算机世界把1970年1月1号定为基准时间,每个度量单位是毫秒(1秒的千分之一),用long类型的变量表示时间。Date分配Date对象并初始化对象,以表示自从标准基准时间(称为“历元”(epoch),即1970年1月1日08:00:00GMT)以来的指定毫秒数。示例:packagecn.tanjian
Wesley13 Wesley13
2年前
java基础知识随身记
2018年11月12日20:51:35一、基础知识:1、JVM、JRE和JDK的区别:JVM(JavaVirtualMachine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。JRE(JavaRuntimeEnvironment):java的运行环境,包括jvmjava的核心类
红烧土豆泥 红烧土豆泥
2年前
Unsafe -- Java的魔法类(一)
UnsafeJava的魔法类(一):::tip原创不易,转载请注明来源:::一、简介​Unsafe,顾名思义,不安全的;Unsafe类位于sun.misc包下,执行低级、不安全操作的方法集合。对于号称是安全的编程语言Java来说,无疑它是个例外,不仅仅是因为它可以堆内存进行操作,还是通过非常规化手段获取到对象,还是对线程调度毛手毛脚,总是它不属
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
2年前
Java Unsafe 类
Unsafe类是啥?Java最初被设计为一种安全的受控环境。尽管如此,JavaHotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是丝毫不建议在生产环境中使用这
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这