热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发-AtomicInteger源码分析

CASCAS(Compare-And-Swap,比较并交换)操作是CPU中术语,它保证了操作的原子性。CAS指令需要三个操作数,分别是:V:内存位置(也就是本次操作变量的内存地址);A:旧的预期

CAS

CAS(Compare-And-Swap,比较并交换)操作是CPU中术语,它保证了操作的原子性。CAS指令需要三个操作数,分别是:

V:内存位置(也就是本次操作变量的内存地址);

A:旧的预期值;

B: 操作完成后的新值。

CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,无论是否更新,都会返回V的旧值,整个CAS操作是一个原子操作。在JDK1.5之后,Java程序中才可以使用CAS操作,该操作由sun.misc.Unsafe类里的compareAndSwapXXX()方法包装提供,虚拟机在内部对这些方法做了特殊处理。在JDK1.5中提供了原子变量,如AtomicInteger,AtomicLong等,由并发大师Doug Lea操刀,提供了在单个变量上面不需要锁的线程安全性。现在就让我们走进AtomicInteger的世界中,探究它是如何实现的,并领略大师的风采。

AtomicInteger中原子操作的实现

通过这一小部分的分析,我们就弄明白了AtomicInteger中原子操作的实现,首先看看AtomicInteger中有哪些状态

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersiOnUID= 6214790243416807050L;

//使用Unsafe.compareAndSwapInt来执行修改操作,CAS是通过Unsafe.compareAndSwapXXX()方法实现的
private static final Unsafe unsafe = Unsafe.getUnsafe();
//value在内存中的地址偏移量
private static final long valueOffset;

static {
try {
//获得value的内存地址偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//当前对象代表的值,注意是volatile
private volatile int value;

在这一段代码中,我们需要注意三个方面,也就是AtomicInteger的三个字段:

(1)unsafe字段,AtomicInteger包含了一个Unsafe类的实例,unsafe就是用来实现CAS的;

(2)value字段,表示当前对象代码的基本类型的值,AtomicInteger是int型的线程安全包装类,value就代码了AtomicInteger的值。注意,这个字段是volatile的。

(3)valueOfset,通过字面意思就可以看出来valueOfset是value在内存中的偏移量,也就是在内存中的地址,通过Unsafe.objectFieldOffset(Field f)获取。前面在讲CAS时,我们提到需要操作内存的位置,valueOfset就是这个位置。

AtomicInteger中的CAS

public final boolean compareAndSet(int expect,int update)就是CAS的具体实现,其他所有的原子操作都是依赖于此方法,前面已经提到了,Java中的CAS是通过Unsafe类实现的,这个方法就是通过调用Unsafe.compareAndSwapInt(this,valueOfset,expect,udate)方法来实现的,其中valueOffset就是要操作的内存地址,expect是旧值,update是新值,当且仅当valueOffset内存中的值等于expect时,才用update更新旧值。其代码如下:

/**
* 原子操作
* CAS:Compare-and-Swap
* 如果当前值==expect,则设置新值
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
AtomicInteger提供了好几个原子操作变量的方法:

int addAndGet(int delta)将当前值与给定的值相加,并返回新值
boolean compareAndSet(int expect, int update)当且仅当value==expect,value=update
int decrementAndGet()自减1,并返回新值
   
int getAndAdd(int delta)将当前值与给定值相加,并返回相加之前的值(旧值)
int getAndDecrement()自减1,并返回旧值
int getAndIncrement()自增1,并返回旧值
int getAndSet(int newValue)将当前值与给定值相加,并返回旧值
int incrementAndGet()自增1,并返回新值

可以看到,每一种操作(自增,自减,加给定值)都提供了对称的操作,其中getAndXXX()方法是返回旧值,XXXAndGet()返回的是新值。这些操作的实现方式都是相同的,我们以getAndIncrement()为例,来讲解它是如何实现原子操作的。首先看getAndIncrement()的源码:

/**
* 原子操作,实现自增操作,通过CAS实现,返回自增之前的值
* 实现原理是这样的:
* 1.首先获得当前值,保存到current中,next=current + 1
* 2.如果CAS成功,则返回current
* 如果CAS不成功,这说明刚刚有其他的线程修改了当前值,current已经失效了,next也已经失效了
* 只能重新获取当值,并继续CAS,直到成功为止
*/
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
在上面的注释中已经说的比较明白了,但我还是愿意再啰嗦两句:

(1)首先获得当前值,保存到current中,并把current的下一个值保存到next中,next=current+1

(2)调用compareAndSet(current,next)尝试自增,如果自增成功,则返回current;如果自增失败,则说明已经有其他线程修改value的值,current中的值和next中的值已经失效了,要重新获取当前值,重复刚才的CAS操作,直到成功位置。

可以看到,通过第(2)步的操作,确实实现了多线程下的安全,即使在自增的时候有其他线程修改了当前值,自增操作也不会覆盖已经修改的值,而是在当前最新值的基础上实现自增。演示一下不使用CAS的错误情况和这种实现方式的正确性

a.错误的自增,假设当前value=8,同时有两个线程t1,t2对value执行自增操作,执行顺序如下:


执行完成后,t2的值把t1的值给覆盖了,执行完成后,现在value=9,正确的结果应该是10。在下图中,通过CAS以同样的顺序执行,获得的结果就是正确的:


上面的分析基本上就概括了AtomicInteger实现的全部了。但是CAS并不是没有缺点,概括起来说,CAS就三个缺点:

(1)ABA问题,如果V的初始值是A,在准备赋值的时候检查到它仍然是A,那么能说它没有改变过吗?也许V经历了这样一个过程:它先变成了B,又变成了A,使用CAS检查时以为它没变,其实却变里;

(2)循环时间长,开销大,通过自旋CAS一直在消耗CPU

(3)只能保证一个共享变量的原子操作,当对多个共享变量操作时就无法保证原子性了。

AtomicInteger源码分析

package com.java.source;

import sun.misc.Unsafe;

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersiOnUID= 6214790243416807050L;

//使用Unsafe.compareAndSwapInt来执行修改操作,CAS是通过Unsafe.compareAndSwapXXX()方法实现的
private static final Unsafe unsafe = Unsafe.getUnsafe();
//value在内存中的地址偏移量
private static final long valueOffset;

static {
try {
//获得value的内存地址偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//当前对象代表的值,注意是volatile
private volatile int value;

/*使用给定的值创建对象,也就是把给定的值包装起来*/
public AtomicInteger(int initialValue) {
value = initialValue;
}

/*默认初始化为0*/
public AtomicInteger() {
}

/*getter/setter*/
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);
}

/*原子操作:设定新值,返回旧值,通过CAS完成*/
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}

/**
* 原子操作
* CAS:Compare-and-Swap
* 如果当前值==expect,则设置新值
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

/**
* 原子操作,功能与compareAndSet一样
* 有可能意外失败,且不保证排序,但是调用的代码是完全一样的,JVM又在内部做了手脚?
* 在极少情况下用来替代compareAndSet
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

/**
* 原子操作,实现自增操作,通过CAS实现,返回自增之前的值
* 实现原理是这样的:
* 1.首先获得当前值,保存到current中,next=current + 1
* 2.如果CAS成功,则返回current
* 如果CAS不成功,这说明刚刚有其他的线程修改了当前值,current已经失效了,next也已经失效了
* 只能重新获取当值,并继续CAS,直到成功为止
*/
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

/**
* 原子操作,实现自减,通过CAS实现,返回当前值
* 实现方法同getAndIncrement()相同
*/
public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}

/**
* 原子操作,将当前值增加delta,并返回当前值
* 实现原理同getAndIncrement()相同,只不过一个是增1,一个是增delta
*/
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;
}
}

/*原子操作,增加delta,并返回增加后的操作*/
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());AtomicLong
}


public int intValue() {
return get();
}

public long longValue() {
return (long)get();
}

public float floatValue() {
return (float)get();
}

public double doubleValue() {
return (double)get();
}

}
转载请注明出处:喻红叶《Java并发-AtomicInteger源码分析》


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
  • SpringBoot简单日志配置
     在生产环境中,只打印error级别的错误,在测试环境中,可以调成debugapplication.properties文件##默认使用logbacklogging.level.r ... [详细]
author-avatar
巴黎不快乐123
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有