玩转并发-Atomic包下AtomicInteger和CAS原理
概述
JDK1.5之后的java.util.concurrent.atomic包里,多了一批原子处理类。AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference。主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理.
AtomicInteger
AtomicInteger提供原子操作Integer的类。在Java语言中,i++和++i操作并不是线程安全的,在使用的时候不可避免造成数据不同步,而AtomicInteger提供线程安全的加减操作接口。
AtomicIntefer提供的API:
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
看个栗子:
private static AtomicInteger count = new AtomicInteger(0);
public static void incrment() {
count.incrementAndGet();
}
public static int getCount() {
return count.get();
}
测试类:
public static void main(String[] args) {
Thread writer = new Thread() {
@Override
public void run() {
while(true) {
try {
System.out.println("writer:--"+Integer.toString(getCount()));
incrment();
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Thread reader = new Thread() {
@Override
public void run() {
while(true) {
try {
System.out.println("reader:--"+Integer.toString(getCount()));
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
writer.start();
reader.start();
}
打印结果:
writer:–0
reader:–0
reader:–1
writer:–1
reader:–2
writer:–2
reader:–3
writer:–3
…
我们并没有通过加锁的方式实现数据同步,AtomicInteger可以实现线程安全。AtomicInteger由硬件提供原子操作指令实现,相比显氏锁和synchronized,开销更小速度更快。
AtomicInteger源码剖析
打开源码,我们先看下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); }
}
private volatile int value;
这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。unsafe类是一个可以执行不安全、容易犯错的操作的一个特殊类。虽然Unsafe类中所有方法都是public的,但是这个类只能在一些被信任的代码中使用
unsafe可以执行以下几种操作:
- 分配内存和释放内存,在方法allocateMemory,reallocateMemory,freeMemory中,有点类似c中的malloc,free方法
- 可以定位对象的属性在内存中的的位置,可以修改对象的属性值
- 挂起和恢复线程
- CAS操作(Compare And Swap),比较并交换,是一个原子操作
AtomicInteger中使用的就是unsafe的CAS操作方法
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
*参数o就是要进行CAS操作的对象
*参数offest是内存地址
*参数expected就是期望的值
*参数x就是需要更新到的值
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
valueOffset是用来记录value本身在内存的偏移地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。
注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。
JDK1.8下的AtomicInteger和JDK1.7下的AtomicInteger的实现是不同的:
JDK1.7下的addAndGet方法:
public final int addAndGet(int delta) {
//死循环
for (;;) {
//得到当前值
int current = get();
//当前值加上delta
int next = current + delta;
//使用CAS原子操作确保线程安全
if (compareAndSet(current, next))
return next;
}
}
JDK1.8下的addAndGet方法
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
在jdk1.8中,直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。基本可以断定,Unsafe新增的方法是性能提升的关键。
AtomicInteger原理
乐观锁
核心思路:每次进行某项操作乐观相信没有冲突而不加锁,假如监测到冲突就失败重试,直到成功
悲观锁
java里面synchronized就是悲观锁,它采用排他机制,是一种独占锁。先假设一种最坏的情况"资源是被占用的",并且在占用其间会导致其他需要锁的线程挂起,等待持有锁的线程放锁。
悲观锁的缺点:由于在进程挂起和恢复执行过程中存在着很大的开销,假设一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。所以当数据争用不严重时,乐观锁效果更好。
Java中CAS的实现
CAS就是Compare And Swap的意思,比较并操作。很多CPU支持CAS指令,CAS是项乐观锁技术。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败;失败的线程并不会被挂起,而是被告知这次竞争失败,并可以再次尝试。CAS有三个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当旧的预期值A和内存值V相等,将内存值修改为B,否则什么都不做。
CAS的核心思想:先从内存V处拿出一个值作为预期值A,等到将要将内存值V修改为B时,判断A是否等于V,如果A不等于V了,说明有别的线程改变了内存值,实行快速失败机制,重新尝试以上操作,直到A==V,确保线程安全,再将V改为B
ABA问题
CAS看起来很好,但是会导致“ABA”问题。
CAS算法实现的一个重要前提是取出内存中某时刻的数据,而在下时刻进行比较并替换,那么在这个时间差会导致数据的变化。
假设线程A从内存位置V取出A,此时线程B也从内存位置V取出A,并且线程B进行一些操作,将A变成B,之后又将V位置的数据变成A。这时候线程A进行CAS操作时,发现旧的预期值和内存值相等,之后线程A操作成功。尽管CAS操作成功,但是不代表这个过程没有问题。
解决方案
利用CAS构造显氏锁TryLock
CASLock锁
public class CASLock {
//AtomicInteger是引用类型,final保证堆内存中地址不变,不保证栈内存中引用不改变
private final AtomicInteger value= new AtomicInteger(0);
//持有锁的线程才有权限解锁
private Thread lockedThread;
public void tryLock() throws GetLockException {
boolean success = value.compareAndSet(0, 1);
if(!success) {
throw new GetLockException();
}
lockedThread=Thread.currentThread();
}
public void unLock() {
if(0==value.get()) {
return;
}
if(lockedThread==Thread.currentThread())
value.compareAndSet(1,0);
}
}
GetLockException异常
public class GetLockException extends Exception {
public GetLockException() {
super();
}
public GetLockException(String exception) {
super(exception);
}
}
测试类:
public static void main(String[] args) {
CASLock casLock = new CASLock();
for(int i=0;i<5;i++) {
new Thread() {
@Override
public void run() {
while(true) {
try {
casLock.tryLock();
//业务逻辑
doSomething();
} catch (GetLockException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
casLock.unLock();}
}
}
}.start();
}
}
打印结果:
at com.Reyco.MyThread.CASLock.tryLock(CASLock.java:12)
at com.Reyco.MyThread.Test$1.run(Test.java:13)
com.Reyco.MyThread.GetLockException
at com.Reyco.MyThread.CASLock.tryLock(CASLock.java:12)
at com.Reyco.MyThread.Test$1.run(Test.java:13)
com.Reyco.MyThread.GetLockException
…
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 解决Keras的自定义lambda层去reshape张量时model保存出错问题
- 解决Keras中Embedding层masking与Concatenate层不可调和的问题
- 浅谈keras使用预训练模型vgg16分类,损失和准确度不变
- 关于tf.matmul() 和tf.multiply() 的区别说明
- python中执行smtplib失败的处理方法
- PHP+Ajax简单get验证操作示例
- Python matplotlib读取excel数据并用for循环画多个子图subplot操作
- python转化excel数字日期为标准日期操作
- thinkPHP框架通过Redis实现增删改查操作的方法详解
- PHP中引用类型和值类型功能与用法示例
- PHP文件上传小程序 适合初学者学习!
- php的扩展写法总结
- 实例介绍PHP删除数组中的重复元素
- Python迭代器协议及for循环工作机制详解
- PHP CURL中传递cookie的方法步骤