深入解析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. [转]获取JAVA[WEB]项目相关路径的几种方法

    http://blog.csdn.net/yaerfeng/article/details/7297479/ 在jsp和class文件中调用的相对路径不同. 在jsp里,根目录是WebRoot 在cl ...

  2. APUE信号-程序汇总

    APUE信号-程序汇总      近期重看APUE,发现对于非常多程序的要领还是没有全然理解.所以梳理下便于查看,并且有非常多值得思考的问题. 程序清单10- 1  捕获 SIGUSR1 和 SIGU ...

  3. hadoop脑裂

    今天修改了和journalNode通信的zookeeper配置,原来没有打开zookeeper动态清理快照的功能. 所以3台zookeeper节点,每台修改完配置后,然后重启了下zookeeper服务 ...

  4. jmap查看内存使用情况与生成heapdump

    jmap查看内存使用情况与生成heapdump 如果想分析自己的JAVA Application时,可以使用jmap程序来生成heapdump文例: jmap -heap 1234  (1234为进程 ...

  5. 【C】——可变参数

    写代码之前要先介绍一下可变参数的备用知识: C函数要在程序中用到以下这些宏: void va_start( va_list arg_ptr, prev_param ); type va_arg( va ...

  6. CSS(八):定位属性

    一.position属性 1.relative(相对定位) 相对它原来的位置,通过指定偏移,到达新的位置. 扔在标准流中,它对父级盒子和相邻的盒子都没有任何影响. 看下面的例子: <!DOCTY ...

  7. HTML(四):行级标签和块级标签

    一.行级标签 行级标签又称为内联标签,行级标签不会单独占据一行,设置宽高无效,行内内部可以容纳其他行内元素,但不可以容纳块元素,不然会出现无法预知的效果. 常见行级标签: span.strong.em ...

  8. 配置文件报错:不允许有匹配 [xX][mM][lL] 的处理指令目标。

    http://www.68idc.cn/help/buildlang/ask/20150108163110.html ————————————————————————————————————————— ...

  9. .gitignore不生效解决办法

    .gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的.那么解决方法就是先把本地缓存删除(改变成未track状态),然后再提 ...

  10. 获取最后插入的id另外方法

    在此记录备忘. CREATE TABLE tb_test(custid INT IDENTITY(1,1) NOT NULL , name nvarchar(200) NOT NULL) DECLAR ...