关闭

线程进阶:多任务处理(17)——Java中的锁(Unsafe基础)

标签: java多线程Unsafe
14086人阅读 评论(13) 收藏 举报
分类:

1. 概述

本专题在之前的文章中详细介绍了Java中最常使用的一种锁机制——同步锁。但是同步锁肯定是不适合在所有应用场景中使用的。所以从本文开始,笔者将试图通过两到三篇文章的篇幅向读者介绍Java中锁的分类、原理和底层实现。以便大家在实际工作中根据应用场景进行使用。本篇文章我们先介绍Java中关于锁的底层实现的基础类sun.misc.Unsafe。

2. Unsafe原子操作

在介绍Java中除同步锁以外的其它锁机制前,我们要介绍一个Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类。请注意,JDK 1.8和之前JDK版本的中sun.misc.Unsafe类可提供的方法有较大的变化,而本文的讲解主要是基于JDK 1.8进行的。sun.misc.Unsafe类提供的原子操作基于操作系统直接对CPU进行操作,而以下这些方法又是sun.misc.Unsafe类中经常被使用的:

2-1. 在你的代码中使用Unsafe

Java语言出于封装性和安全性的考虑,它不允许技术人员直接使用Unsafe类和其中的各种方法,特别是不能直接使用“new”的方式对sun.misc.Unsafe类进行实例化(否则会报告“java.lang.SecurityException: Unsafe”错误)。但是你可以通过Java提供的反射机制获取到Unsafe类的实例:

……
Field f = null;
sun.misc.Unsafe unsafe = null;
try {
    f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    // 得到Unsafe类的实例
    unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}
……

2-2. Unsafe典型方法

2-2-1. Unsafe.objectFieldOffset(Field) 和类似方法

Unsafe中除了objectFieldOffset(Field) 这个方法外,还有一个类似的方法staticFieldOffset(Field)。这两个方法用于返回类定义中某个属性在主存中设定的偏移量。请看以下代码:

……
// 注意本代码中的unsafe对象就是根据之前代码获取到的

// 开始使用unsafe对象,分别找到UserPojo对象中child属性和name属性的内存地址偏移量
// 首先是UserPojo类中的child属性,在内存中设定的偏移位置
Field field = UserPojo.class.getDeclaredField("child");
// 这就是一旦这个类实例化后,该属性在内存中的偏移位置
long offset = unsafe.objectFieldOffset(field);
System.out.println("child offset = " + offset);

// 然后是UserPojo类中的name属性,在内存中设定的偏移位置
Field fieldName = UserPojo.class.getDeclaredField("name");
long nameOffset = unsafe.objectFieldOffset(fieldName);
System.out.println("name offset = " + nameOffset);
……

这两个方法相比后面要介绍的各种Unsafe方法来说,显得更为重要。因为Unsafe对象中属性的操作方式都是直接通过内存偏移量的方式找到操作目标。

2-2-2. Unsafe.compareAndSwapObject(Object, long, Object, Object)和类似方法

Unsafe中除了compareAndSwapObject 这个方法外,还有两个类似的方法:unsafe.compareAndSwapInt和unsafe.compareAndSwapLong。这些方法的作用就是对属性进行比较并替换(俗称的CAS过程——Compare And Swap)。当给定的对象中,指定属性的值符合预期,则将这个值替换成一个新的值并且返回true;否则就忽略这个替换操作并且返回false。

请注意CAS过程是sun.misc.Unsafe类中除了获取内存偏移量以外,提供的最重要的功能了——因为Java中很多基于“无同步锁”方式的功能实现原理都是基于CAS过程。请看如下示例代码:

……
UserPojo user = new UserPojo();
user.setName("yinwenjie");
user.setSex(11);
user.setUserId("userid");

// 获得sex属性的内存地址偏移量 
Field field = UserPojo.class.getDeclaredField("sex");
long sexOffset = unsafe.objectFieldOffset(field);

/* 
 * 比较并修改值
 * 1、需要修改的对象
 * 2、要更改的属性的内存偏移量
 * 3、预期的值
 * 4、设置的新值
 * */
// 为什么是Object而不是int呢?因为sex属性的类型是Integer不是int嘛
if(unsafe.compareAndSwapObject(user, sexOffset, 11, 13)) {
    System.out.println("更改成功!");
} else {
    System.out.println("更改失败!");
}
……

首先创建一个UserPojo类的实例对象,这个实例对象有三个属性name、sex和userId。接着我们找到sex属性在主存中设定的偏移量sexOffset,并进行CAS操作。请注意compareAndSwapObject方法的四个值:第一个值表示要进行操作的对象user,第二个参数通过之前获取的主存偏移量sexOffset告诉方法将要比较的是user对象中的哪个属性,第三个参数为技术人员所预想的该属性的当前值,第四个参数为将要替换成的新值。

那么将方法套用到以上的compareAndSwapObject执行过程中:如果当前user对象中sex属性为11,则将这个sex属性的值替换为13,并返回true;否则不替换sex属性的值,并且返回false。

2-2-3. Unsafe.getAndAddInt(Object, long, int)和类似方法

类似的方法还有getAndAddLong(Object, long, long),它们的作用是利用Unsafe的原子操作性,向调用者返回某个属性当前的值,并且紧接着将这个属性增加一个新的值。在java.util.concurrent.atomic代码包中,有一个类AtomicInteger,这个类用于进行基于原子操作的线程安全的计数操作,且这个类在JDK1.8+的版本中进行了较大的修改。以下代码示例了该类的getAndIncrement()方法中的实现片段:

……
public class AtomicInteger extends Number implements java.io.Serializable {
    ……
    private volatile int value;
    ……
    private static final long valueOffset;
    ……
    // 获取到value属性的内存偏移量valueOffset
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    ……
    /**
     * 这是JDK1.8中的实现
     * Atomically increments by one the current value.
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    ……
}

通过以上代码的演示可以看到,AtomicInteger类中定义了一个value属性,并通过unsafe.objectFieldOffset方法获取到了这个属性在主存中设定的偏移量valueOffset。接着就可以在getAndIncrement方法中直接使用unsafe.getAndAddInt的方式,通过偏移量valueOffset将value属性的值加“1”。但是该方法的实现在JDK1.8之前的版本中,实现代码却是这样的:

// 获取偏移量valueOffset的代码类似,这里就不再展示了
……
public final int getAndIncrement() {
    // 一直循环,直到
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}
……
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
……

代码中采用的就是基于Unsafe的“乐观锁”进行实现的,后文中我们还会讲解java的自旋锁以及自旋锁的一种具体实现方式“乐观锁”,还会分析“乐观锁”适合使用的场景。在以上代码中,getAndIncrement方法内部会不停的循环,直到unsafe.compareAndSwapInt方法执行成功。但多数情况下,循环只会执行一次,因为多线程强占同一对象属性的情况并不是随时都会出现。

2-2-4. 其它典型方法

在JDK1.8中Unsafe还有一些其它实用的原子操作方法:

  • PutXXXXX(Object, long, short)

    类似的方法包括:putInt(Object, long, int)、putBoolean(Object, long, boolean)、putShort(Object, long, short)、putChar(Object, long, char)、putDouble(Object, long, double)等,这些都是针对指定对象中在偏移量上的属性值,进行直接设定。这些操作发生在CPU一级缓存(L1) 或者二级缓存(L2)中,但是这些方法并不保证工作在其它内核上的线程“立即看到”最新的属性值。

  • putXXXXXVolatile(Object, long, byte)

    类似的方法包括:putByteVolatile(Object, long, byte)、putShortVolatile(Object, long, short)、putFloatVolatile(Object, long, float)、putDoubleVolatile(Object, long, double)等等,这些方法的主要作用虽然也是直接针对偏移量改变指定对象中的属性值,但是这些方法保证工作在其它内核上的线程能“立即看到”最新的属性值——也就是说这些方法满足volatile语义(后续文章会详细介绍volatile的详细工作原理)

===============(接下文)

7
0
查看评论

并发编程--原子类AotmicInteger

前几篇博客中我们已经介绍了线程、volatile、synchronized和cas自旋相关的知识,接下来我介绍一下jdk提供的并发编程包java.util.concurrent中相关的实现类知识。 AtomicInteger简单来说就是一个能进行原子操作的Integer,这样在多线程操作下对Atom...
  • qq924862077
  • qq924862077
  • 2017-04-05 18:11
  • 596

Java之美[从菜鸟到高手演练]之atomic包的原理及分析

作者:二青个人站点:zhangerqing.cn    邮箱:xtfggef@gmail.com    微博:http://weibo.com/xtfggefAtomic简介Atomic包是java.util.concurrent下的另一个专门为线程...
  • zhangerqing
  • zhangerqing
  • 2015-01-24 11:00
  • 65918

java Unsafe.java(一)

探究java.misc.Unsafe 类
  • hezuideda
  • hezuideda
  • 2015-04-19 20:32
  • 1823

错误解决: java.lang.SecurityException: Permission Denial

场景:  java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.siveco.bluebee.phone.supervisor/com.siveco.bluebee.core.activity.G...
  • knighttools
  • knighttools
  • 2014-11-20 12:03
  • 62879

java高并发:CAS无锁原理及广泛应用

前言在现在的互联网技术领域,用户流量越来越大,系统中并发量越来越大,大公司的日活动辄成百上千万。如何面对如此高的并发是当今互联网技术圈一直在努力的事情。 应对高并发需要在各个技术层面进行合理的设计和技术选型才可以。本文只讲述微观层面是如何应对多线程高并发的,介绍著名的CAS原理以及其广泛应用。 ...
  • fgyibupi
  • fgyibupi
  • 2016-12-20 12:53
  • 5904

【我的Android进阶之旅】Android 7.0报异常:java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated;

之前开发的一个和第三方合作的apk,在之前公司的 Android 5.1 系统的手表上运行正常,今天在公司新开发的 Android 7.1系统的手表上运行的时候,使用 DownloadManager 下载之后,查询下载状态的时候,报了异常java.lang.SecurityException: CO...
  • qq446282412
  • qq446282412
  • 2017-03-17 16:57
  • 4610

Unsafe

来源:http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/ Java是一门安全的编程语言,并且防范程序员犯大量的愚蠢的错误,它们中的大部分是内存管理。但我们可以使用Unsafe类来故意犯这类错误。 这篇文章会快速...
  • JJYWEN
  • JJYWEN
  • 2016-04-15 23:25
  • 4783

Java --- Unsafe

Java --- Unsafe 初步介绍 在AQS,Netty和Guava的源码中出现了sun.misc.Unsafe 的身影。Unsafe类的定义是:执行底层,不安全的操作的方法的集合,所有的方法都是Native的。主要分为以下几类方法: 内存操作: addressSize,allocat...
  • dreamsofa
  • dreamsofa
  • 2015-10-09 16:19
  • 1151

危险代码:如何使用Unsafe操作内存中的Java类和对象

本文由 ImportNew - 吴际 翻译自 zeroturnaround。如需转载本文,请先参见文章末尾处的转载要求。 ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Java开发 小组。参与方式请...
  • novelly
  • novelly
  • 2014-01-23 13:43
  • 1214

Java Unsafe 类

Unsafe类是啥?Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.nio和java.util.concurrent...
  • zhxdick
  • zhxdick
  • 2016-07-23 15:45
  • 4509
    个人资料
    • 访问:1121134次
    • 积分:10953
    • 等级:
    • 排名:第1759名
    • 原创:118篇
    • 转载:1篇
    • 译文:0篇
    • 评论:1131条
    博客专栏