Java开发中不可避免的会遇到并发的问题。在进行并发编程的时候我们需要确保程序在被多个线程并发访问时可以得到正确的结果,也就是要实现线程安全。

那么什么样的标准可以称为线程安全呢?这里有线程安全的定义:

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

举一个线程不安全的小例子。假如我们想实现一个功能来统计网页访问量,首先我们可能想到用count++ 的方法来统计访问量。count++ 其实可以分成三个独立的操作:

  1. 获取变量当前值
  2. 给获取的当前变量值+1
  3. 写回新的值到变量

假设count的初始值为10,当进行并发操作的时候,可能出现线程A和线程B都进行到了1操作,之后又同时进行2操作。A先进行到3操作+1,现在值为11;注意刚才AB获取到的当前值都是10,所以B执行3操作后,count的值依然是11。这个结果显然不符合我们的要求。因此这个count++操作不是线程安全的。

实现线程安全的目标,我们需要引入本篇的主角—— AtomicInteger 。本篇我们介绍AtomicInteger原子类型内部是如何实现线程安全的。

不多说,先看看AtomicInteger 的源码:

package java.util.concurrent.atomic;
import sun.misc.Unsafe; public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile int value; public AtomicInteger(int initialValue) {
value = initialValue;
} public AtomicInteger() {
} public final int get() {
return value;
} public final void set(int newValue) {
value = newValue;
} public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
} public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
} public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
} public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
} public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
} public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
} public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
} public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
} public String toString() {
return Integer.toString(get());
} public int intValue() {
return get();
} public long longValue() {
return (long)get();
} public float floatValue() {
return (float)get();
} public double doubleValue() {
return (double)get();
} }

一、AtomicInteger中定义的属性

   // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

第一个变量是Unsafe,Unsafe是JDK内部的工具类,主要实现了平台相关的操作。下面内容引自JDK官方文档:

sun.misc.Unsafe是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。

Unsafe的具体实现跟本篇的目标关联不大,你只要知道这段代码是为了获取value在堆内存中的偏移量就够了。

第二个变量是valueOffset,也就是内存偏移量。偏移量在AtomicInteger中很重要,AtomicInteger的原子操作都靠内存偏移量来实现的。

二、Value的定义和volatile

AtomicInteger 本身是个整型,所以最重要的属性就是value,我们看看它是如何声明value的

 private volatile int value;

我们看到value使用了volatile 修饰符,那么什么是volatile呢?

volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。

volatile包含以下语义:

  1. Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
  2. volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。

简而言之volatile 的作用是当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值。在分析AtomicInteger 源码时,我们了解到这里就足够了。

三、用CAS操作实现安全的自增

AtomicInteger中有很多方法,例如incrementAndGet() 相当于i++getAndAdd() 相当于i+=n 。从源码中我们可以看出这几种方法的实现很相似,所以我们主要分析incrementAndGet() 方法的源码。

源码如下:

 public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
} public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

incrementAndGet() 方法实现了自增的操作。核心实现是先获取当前值和目标值(也就是value+1),如果compareAndSet(current, next) 返回成功则该方法返回目标值。那么compareAndSet是做什么的呢?理解这个方法我们需要引入CAS操作。

在大学操作系统课程中我们学过独占锁和乐观锁的概念。独占锁就是线程获取锁后其他的线程都需要挂起,直到持有独占锁的线程释放锁;乐观锁是先假定没有冲突直接进行操作,如果因为有冲突而失败就重试,直到操作成功。其中乐观锁用到的机制就是CAS,Compare and Swap。

AtomicInteger 中的CAS操作就是compareAndSet(),其作用是每次从内存中根据内存偏移量(valueOffset)取出数据,将取出的值跟expect 比较,如果数据一致就把内存中的值改为update。

这样使用CAS就保证了原子操作。其余几个方法的原理跟这个相同,在此不再过多的解释。

没看AtomicInteger 源码之前,我认为其内部是用synchronized 来实现的原子操作。查阅资料后发现synchronized 会影响性能,因为Java中的synchronized 锁是独占锁,虽然可以实现原子操作,但是这种实现方式的并发性能很差。

四、总结

总结一下,AtomicInteger 中主要实现了整型的原子操作,防止并发情况下出现异常结果,其内部主要依靠JDK 中的unsafe 类操作内存中的数据来实现的。volatile 修饰符保证了value在内存中其他线程可以看到其值得改变。CAS操作保证了AtomicInteger 可以安全的修改value 的值。

深入解析Java AtomicInteger 原子类型的更多相关文章

  1. 深入解析Java AtomicInteger原子类型

    深入解析Java AtomicInteger原子类型 在并发编程中,需要确保当多个线程同时访问时,程序能够获得正确的结果,即实现线程安全.线程安全性定义如下: 当多个线程访问一个类时,无论如何调度运行 ...

  2. 深度解析Java可变参数类型以及与数组的区别

    注意:可变参数类型是在jdk1.5版本的新特性,数组类型是jdk1.0就有了. 这篇文章主要介绍了Java方法的可变参数类型,通过实例对Java中的可变参数类型进行了较为深入的分析,需要的朋友可以参考 ...

  3. 原子类型的使用&Unsafe&CAS

    在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作). 下面以Ato ...

  4. 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类

    这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. watermark/2/text/aHR0cDovL2Jsb2cu ...

  5. 对着java并发包写.net并发包之原子类型实现

    众所周知,java1.5并发包通过volatile+CAS原理提供了优雅的并发支持.今天仔细想想.net也有volatile关键字保证内存的可见性,同时也有Interlocked提供了CAS的API, ...

  6. 基础篇:深入解析JAVA泛型和Type类型体系

    目录 1 JAVA的Type类型体系 2 泛型的概念 3 泛型类和泛型方法的示例 4 类型擦除 5 参数化类型ParameterizedType 6 泛型的继承 7 泛型变量TypeVariable ...

  7. 转 : 深入解析Java锁机制

    深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...

  8. Java多线程-----原子变量和CAS算法

       原子变量      原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题      Java给我们提供了以下几种原子类型: AtomicInteger和Ato ...

  9. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

随机推荐

  1. BZOJ4386[POI2015]Wycieczki——矩阵乘法+倍增

    题目描述 给定一张n个点m条边的带权有向图,每条边的边权只可能是1,2,3中的一种.将所有可能的路径按路径长度排序,请输出第k小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点. 输入 第 ...

  2. LOJ #6436. 「PKUSC2018」神仙的游戏(字符串+NTT)

    题面 LOJ #6436. 「PKUSC2018」神仙的游戏 题解 参考 yyb 的口中的长郡最强选手 租酥雨大佬的博客 ... 一开始以为 通配符匹配 就是类似于 BZOJ 4259: 残缺的字符串 ...

  3. [luogu3978][bzoj4001][TJOI2005]概率论【基尔霍夫矩阵+卡特兰数】

    题目描述 为了提高智商,ZJY开始学习概率论.有一天,她想到了这样一个问题:对于一棵随机生成的n个结点的有根二叉树(所有互相不同构的形态等概率出现),它的叶子节点数的期望是多少呢? 判断两棵树是否同构 ...

  4. Status: Checked in and viewable by authorized users 出现在sharepoint 2013 home 页面

    点击home页面上方的publish-> publishing->publish

  5. luogu2577/bzoj1899 午餐 (贪心+dp)

    首先,应该尽量让吃饭慢的排在前面,先按这个排个序 然后再来决定每个人到底去哪边 设f[i][j]是做到了第i个人,然后1号窗口目前的总排队时间是j,目前的最大总时间 有这个i和j的话,再预处理出前i个 ...

  6. git 28原则

    一.流程 $ git init # 创建一个新的仓库 sublime 编写文本,不要使用win自带文本编辑器 $ git add file1 # 将文件添加到暂存区 $ git add file2 $ ...

  7. qrcode模块简单使用

    函数式自动生成二维码 import qrcode img = qrcode.make("hello world!") img.get_image().show() img.save ...

  8. 利用Springboot-mail发送邮件

    相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送.在Spring Boot的Starter模块中也为此提供了自动化配置.下面通过实例看看 ...

  9. intest

    /* ============================================================================ Name : http.c Author ...

  10. module模块和包(十七)

    在前面的几个章节中我们脚本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了. 为此 Python 提供了一个办法,把这些定义存放在文 ...