深入解析Java AtomicInteger原子类型
深入解析Java AtomicInteger原子类型
在并发编程中,需要确保当多个线程同时访问时,程序能够获得正确的结果,即实现线程安全。线程安全性定义如下:
当多个线程访问一个类时,无论如何调度运行时环境或如何交替执行这些线程,并且主代码中不需要额外的同步或协作,该类都可以正确地运行,因此该类是线程安全的。
线程安全需要两件事:
- 保证线程的内存可见性
- 保证原子性
以线程不安全性为例。如果我们想要实现一个函数来对页面访问进行计数,那么您可能想要count+,但是这个增量操作不是线程安全的。Count++可以分为三个操作:
- 获取变量当前值
- 给获取的当前变量值+1
- 写回新的值到变量
假设计数的初始值为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包含以下语义:
- Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
- 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原子类型的更多相关文章
- 深入解析Java AtomicInteger 原子类型
深入解析Java AtomicInteger原子类型 在进行并发编程的时候我们需要确保程序在被多个线程并发访问时可以得到正确的结果,也就是实现线程安全.线程安全的定义如下: 当多个线程访问某个类时,不 ...
- 深度解析Java可变参数类型以及与数组的区别
注意:可变参数类型是在jdk1.5版本的新特性,数组类型是jdk1.0就有了. 这篇文章主要介绍了Java方法的可变参数类型,通过实例对Java中的可变参数类型进行了较为深入的分析,需要的朋友可以参考 ...
- 原子类型的使用&Unsafe&CAS
在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作). 下面以Ato ...
- 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类
这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. watermark/2/text/aHR0cDovL2Jsb2cu ...
- 对着java并发包写.net并发包之原子类型实现
众所周知,java1.5并发包通过volatile+CAS原理提供了优雅的并发支持.今天仔细想想.net也有volatile关键字保证内存的可见性,同时也有Interlocked提供了CAS的API, ...
- 基础篇:深入解析JAVA泛型和Type类型体系
目录 1 JAVA的Type类型体系 2 泛型的概念 3 泛型类和泛型方法的示例 4 类型擦除 5 参数化类型ParameterizedType 6 泛型的继承 7 泛型变量TypeVariable ...
- 转 : 深入解析Java锁机制
深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...
- Java多线程-----原子变量和CAS算法
原子变量 原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题 Java给我们提供了以下几种原子类型: AtomicInteger和Ato ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
随机推荐
- EF中的1:0或1:1关系以及1:n关系
先给出1:0关系 User表包括用户名和密码 public class User { public int ID { get; set; } public string UserName { get; ...
- iOS内存管理和优化 from 刘延军
- Android——简易计算器(转)
这是我的第一个andriod小程序,第一次写用了半个月,第二次修改用了一天,第三次修改用了两个小时,现在终于比较满意了.现在我就直接分享一下我的源代码,由于思路比较简单,注释加的不多.采用的是相对布局 ...
- go包管理之glide
go语言的包是没有中央库来统一管理的,通过使用go get命令从远程代码库(github.com,goolge code 等)拉取,直接跳过中央版本库的约束,让代码的拉取直接基于源代码版本控制库,开发 ...
- uboot下的网络终端/控制台
许多linux设备可能没有外置串口,这是就需要一个网络终端来在uboot下操作设备,如升级镜像等. uboot下的网络终端为netconsole,代码drivers/net/netconsole.c. ...
- hbase源码带注释版本,放在这里,方便大家下载吧
看了5个月的hbase源码,记录了一些笔记,如果有需要的朋友可以拿去. 里面总共包括几个主要的工程吧:hbase-common,hbase-client,hbase-prefix-tree,hbase ...
- Redis列表
Redis列表是简单的字符串列表,排序插入顺序.您可以在头部或列表的尾部Redis的列表添加元素. 列表的最大长度为232 – 1 (每个列表超过4十亿元素4294967295)元素. 例子 redi ...
- 【C】——setjmp练习
#include<setjmp.h> int setjmp(jmp_buf env); 返回值:若直接调用则返回0,若从longjmp调用返回则返回非0值 void longjmp(jmp ...
- svn 备份脚本
[root@hm-vpnserver-196 ~]# cat /root/svnback.sh #!/bin/bashtt=$(ls /home/svndata/) for i in $ttdo mk ...
- JavaScript(四):函数
JavaScript中的函数分为两种:系统函数和自定义函数,这里主要讲解自定义函数. 一.自定义函数 1.语法: 注意: 传入的参数是可选的. 例如: <!DOCTYPE html> &l ...