深入解析Java AtomicInteger原子类型

在并发编程中,需要确保当多个线程同时访问时,程序能够获得正确的结果,即实现线程安全。线程安全性定义如下:

当多个线程访问一个类时,无论如何调度运行时环境或如何交替执行这些线程,并且主代码中不需要额外的同步或协作,该类都可以正确地运行,因此该类是线程安全的。

线程安全需要两件事:

  1. 保证线程的内存可见性
  2. 保证原子性

以线程不安全性为例。如果我们想要实现一个函数来对页面访问进行计数,那么您可能想要count+,但是这个增量操作不是线程安全的。Count++可以分为三个操作:

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

假设计数的初始值为10,当执行并发操作时,线程A和B可以同时进行1次操作,然后进行2次操作。A前进到3+1,当前值为11。注意,AB刚才获得的当前值是10,所以在B执行3次操作之后,计数仍然是11。这个结果显然不符合我们的要求。

因此,我们需要使用本文的主角Atomic Integer来确保线程安全。

Atomic Integer的源代码如下:

 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 类中定义的属性

不安全是JDK中的一个工具类,它主要实现与平台相关的操作。以下引用自

太阳。杂项。不安全是JDK中使用的工具类。通过将Java意义上的一些“不安全”功能暴露给Java层代码,JDK可以更多地使用Java代码来实现与平台相关的一些功能,并且需要使用本机语言(如C或C++)来实现。这个类不应该在JDK核心类库之外使用。

不安全的实现与本文的目标几乎没有关系。您只需要知道这个代码是获取堆内存中的值偏移量。偏移量在原子整数中非常重要。原子操作依赖于它。

Value的定义和volatile

AtomicInteger本身是一个整数,因此最重要的属性是值。让我们看看它是如何声明值的。

  private volatile int value;

我们看到值使用易失性修饰符,那么什么是易失性呢?

易失性相当于同步的弱实现,也就是说,易失性实现了类似于同步的语义而无锁定机制。它确保以可预测的方式将易失性字段的更新传递给其他线程。

Volatile包含以下语义:

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

volatile 主要特性有两点:1.防止重排序。2. 实现内存可见性 。内存可见性的作用是当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值。在分析AtomicInteger 源码时,我们了解到这里就足够了。

用CAS操作实现原子性

Atomic Integer中有很多方法,比如等价于i++的.mentAndGet()和等价于i+=n的getAndAdd()。

源码如下:

  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是用于乐观锁定的机制,称为比较和交换。

Atomic Integer中的CAS操作是.eAndSet(),其功能是每次根据值Offset从内存中检索数据,并将检索到的值与expect进行比较。如果数据是一致的,则将内存中的值更改为更新。如果数据不一致,则意味着内存中的数据已经更新,然后将回滚(期望值无效)。

这样,使用CAS方法保证了原子操作。Atomic Long、Atomic Boolean和Java中的其他方法的基本原理和思想与原子整数的基本原理和思想基本相同。这篇文章不会解释太多。
你可能会问,同步关键字也可以实现并发操作啊,为什么不使用同步呢?

事实上,在我研究Atomic Integer源代码之前,我认为它是通过同步的原子操作在内部实现的。后来,搜索发现,原子操作的原始同步实现会影响性能,因为Java中的同步锁是排他锁,虽然可以实现原子操作,但是这种实现的并发性能很差。

总结

总之,Atomic Integer主要实现线程安全的整数操作,以防止并发情况下出现异常结果。其内部实现主要依赖于JDK中易失性和不安全的类操作存储器数据。易失性修饰符确保了内存中的其他线程能够看到值得更改的值,即实现了内存可见性。CAS操作确保了原子整数可以安全地修改值,实现原子性。volatile和CAS操作的组合实现了线程安全。

深入解析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. vue-router "path" is required in a route configuration

    启用了动态路由,一直提示这个错误,页面打开也是空白,后来发现原来是component参数错误. 正确的写法为: component: () => import ('@/views/own-spa ...

  2. java框架篇---struts实现拦截器

    Struts2的拦截器和Servlet过滤器类似.在执行Action的execute方法之前,Struts2会首先执行在struts.xml中引用的拦截器,在执行完所有引用的拦截器的intercept ...

  3. nginx中的break与last指令区别

    很多人资料说,last与break的区别在于,last并不会停止对下面location的匹配.我理解上模模糊糊.今天自己来测验了一下. rewrite 指令末尾的break应该与单独写break作用是 ...

  4. mongo 操作小结

    这里总结一下mongo常用操作语句,分享给大家和我自己~ 打印系统,数据库,集合的信息 db.stats()                                    打印数据库状态 db ...

  5. PHP——大话PHP设计模式——链式操作

  6. kafka生产消费原理笔记

    一.什么是kafka Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性 ...

  7. 聊聊Java中的拆箱和装箱操作

    在刷谷歌面试题的过程中,发现一道有意思的题目,以前没有特别注意,忽略了一些东西,特此记录. 题目要求输出以下代码的结果: public class MyTest { public static voi ...

  8. [算法]和为S的两个数字

    题目描述 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的. 对应每个测试案例,输出两个数,小的先输出. 思路 定义两个指 ...

  9. Android训练课程(Android Training) - 添加活动栏(使用action bar)

    2014-10-28 张云飞VIR 翻译自:https://developer.android.com/training/basics/actionbar/index.html 添加活动栏(Addin ...

  10. jQuery(十一):jQuery的事件

    一.jQuery事件的分类 jQuery事件是对JavaScript事件的封装,常用事件分类如下: 1.基础事件 window事件. 鼠标事件. 键盘事件. 表单事件. 2.复合事件是多个事件的组合 ...