【第22期】观点:IT 行业加班,到底有没有价值?

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

原创 2017年06月14日 18:41:06

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的详细工作原理)

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

版权声明:欢迎转载,但是看在我辛勤劳动的份上,请注明来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!) 举报

相关文章推荐

线程基础:线程(2)——JAVA中的基本线程操作(上)

从这篇文章开始。我们介绍线程的工作原理。

线程基础:线程(1)——操作系统和线程原理

本人不才,应读者要求新开专栏,与各位读者分享自己对JAVA线程技术的理解和使用经验。这个专栏将分成两个部分:线程基础知识和锁知识。专栏的难度应该是我所开专栏中难度最低的一个,着重于线程基础知识的讲解,...

程序员升职加薪指南!还缺一个“证”!

CSDN出品,立即查看!

架构设计:系统间通信(1)——概述从“聊天”开始上篇

这个文章系列专门整理总结了目前系统间通信的主要原理、手段和实现。我们将讲解典型的信息格式、讲解JAVA RMI调用并延伸出来重点讲解RPC调用和使用案例;最后我们还会讲到SOA架构的实现,包括ESB实...

FastDFS安装、配置、部署(一)-安装和部署

FastDFS是一个开源的,高性能的的分布式文件系统,他主要的功能包括:文件存储,同步和访问,设计基于高可用和负载均衡,FastDFS非常适用于基于文件服务的站点,例如图片分享和视频分享网站FastD...

fastdfs 环境搭建使用及最佳实践

一、分布式文件系统 FastDFS 架构解析FastDFS是为互联网应用量身定做的一套分布式文件存储系统,非常适合用来存储用户图片、视频、文档等文件,许多大型互联网公司包括几个大的云网盘都在使用,感兴...

FastDFS+Nginx安装配置笔记

1.系统环境   最小化安装的 CentOS 6.2 tracker1:   192.168.68.113 tracker1:   192.168.68.116 storag...

每周荐书:Kotlin、分布式、Keras(评论送书)

每周荐书:Kotlin、分布式、Keras(评论送书) 感谢大家对每周荐书栏目的支持,先公布下上周中奖名单 法式三文鱼 FontTianPython机器学习算法 bit_kaki远东悟语揭秘Java虚...

架构设计:系统存储(21)——图片服务器:详细设计(1)

之前的两篇文章介绍了图片系统的技术组件选型和技术方案设计,从这篇文章开始我们将搭建工程进行详细的编码开发和效果测试。整个图片服务工程代码会上传到了CSDN的下载区,如果对工程感兴趣那么读者可以直接下载...

架构设计:系统存储(29)——分布式文件系统Ceph(管理)

从本篇文章开始介绍一款现在非常火的分布式文件系统Ceph,包括这款文件系统的安装、基本使用场景、常用管理命令和重要工作原理。特别是讨论了PaxOS算法的基本理后,就更容易理解Ceph分布式文件系统中各...

架构设计:系统存储(30)——分布式文件系统Ceph(RADOS结构)

从本篇文章开始介绍一款现在非常火的分布式文件系统Ceph,包括这款文件系统的安装、基本使用场景、常用管理命令和重要工作原理。特别是讨论了PaxOS算法的基本理后,就更容易理解Ceph分布式文件系统中各...
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)