线程进阶:多任务处理(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(未经允许严禁用于商业用途!)

相关文章推荐

借其他行业的做法来看软件测试和质量保证

今天下午在上海参加了邰晓梅老师和华雄兄组织的ministar club的活动,这里上传一下今天的PPT,  http://download.csdn.net/download/superqa/429...

java数学函数Math类中常用的方法

Math类提供了常用的一些数学函数,如:三角函数、对数、指数等。一个数学公式如果想用代码表示,则可以将其拆分然后一一套用Math类下的方法即可。

每周荐书:Java Web、Python极客编程、移动Web前端(评论送书)

每周荐书:Java Web、Python极客编程、移动Web前端(评论送书) 各位抱歉,上周活动停更一周,本周继续感谢大家对每周荐书栏目的支持,先公布下上期活动中奖名单 微雨燕凝霜寒森林之舟架构探险:...

互联网测试有什么不一样

最近一直有写这样一篇文章的想法,因为自己工作的变动,都是些零散的思路和想法,这里稍作整理,贴出来。正好假期的时候有朋友问到这方面的话题,希望也是一个参考。 其实说实话,觉得自己不是很够资格来写这个,...

java基础(一)

用一个简单的例子记录java基础的学习过程,以备遗忘(截图表示) 接口1: jk1.java文件 package com.java1995; public interface jk1 ...

架构设计:系统存储(28)——分布式文件系统Ceph(挂载)

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

关于移动app测试的一些思考和实践 - 含PPT下载

5月底去杭州参加了一次阿里技术沙龙的活动,应会议组织者耿电兄的邀请去做了一个移动app测试的分享(详见http://club.alibabatech.org)。有点被抓壮丁的感觉,主要是因为觉得我们团...

架构设计:系统存储(1)——块存储方案(1)

在“系统存储”专题中,我们将按照“从上至下”的顺序向读者介绍整个“系统存储”体系。在这个专题中我们将至少介绍机械硬盘的主要结构、硬盘阵列的分类、操作系统的EXT文件系统、NAS文件共享存储方案、分布式...

《J2EE核心模式》第二章——表现层的不佳实现

助手的概念:大多数情况下既是在JSP上帮助信息显示,资源可见性约束的标签,这些标签应该具有以下的性质:1、高度的抽象化:这个可以理解为标签的可描述性和高适应性2、高度的模块化3、高度的重用性:即一处定...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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