网站首页 > java教程 正文
从这一篇文章开始,我开始对并发包的atomic包进行源码解析,首先通过下面的图了解以下atomic包中的类都是有哪些?
这一篇文章首先对AtomicInteger进行讲解。在上一篇文章中我对并发包保证共享变量线程安全的机制做了讲解,如果对关键字volatile和CAS机制真正了解的话,AtomicInteger的内部原理是非常的容易理解的,那就跟着我的思路来打开AtomicInteger的大门吧。

本篇文章的主要内容:
1:AtomicInteger的实例 2:AtomicInteger的源码解析
1、AtomicInteger的实例
我首先利用一个简单的例子引入今天的话题:
public class AtomicIntegerTest {
private static AtomicInteger ai = new AtomicInteger(0);
private static int a = 0;
public static void main(String[] args) throws InterruptedException {
ordinaryIntegerTest();
atomicIntegerTest();
Thread.sleep(2000);
System.out.println("a="+a);
System.out.println("ai="+ai.get());
}
//普通变量多线程下累加
public static void ordinaryIntegerTest(){
for(int i = 0;i<10;i++){
new Thread(()->{
for(int j = 0;j<10000;j++){
a++;
}
}).start();
}
}
//利用AtomicInteger多线程下累加
public static void atomicIntegerTest(){
for(int i = 0;i<10;i++){
new Thread(()->{
for(int j = 0;j<10000;j++){
ai.incrementAndGet();
}
}).start();
}
}
}
通过上面的例子,大家能够猜出答案吗?运行结果如下:
通过上面两个运行结果可以看出:普通的变量每一次运行结果都不相同,而AtomicInteger变量每一次运行结果都是相同的,而且结果是正确的,所以在多线程下,普通变量无法保证线程安全,而AtomicInteger则在多线程下每一次结果都是正确的。那么我们知道了在多线程下AtomicInteger是线程安全的,它内部怎样实现的呢?我们接下来进入AtomicInteger源码一探究竟。
2、AtomicInteger的源码解析
2.1、首先看一下AtomicInteger的定义
public class AtomicInteger extends Number implements java.io.Serializable {
//代码省略
}
//Number类的定义:主要将当前类型转化为基本类型
public abstract class Number implements java.io.Serializable {
//将当前类型转化成int
public abstract int intValue();
//将当前类型转化为long型
public abstract long longValue();
//将当前类型转化为float类型
public abstract float floatValue();
//将当前类型转化为double类型
public abstract double doubleValue();
//将当前类型转化为byte类型
public byte byteValue() {
return (byte)intValue();
}
//将当前类型转为short类型
public short shortValue() {
return (short)intValue();
}
}
2.2、AtomicInteger的成员变量
上一篇文章我说过,在并发包中大部分都是利用关键字volatile和CAS机制进行保证并发安全的,所以大家应该可以才想到在AtomicInteger成员变量中必定会出现被关键字volatile修饰的变量和原子封装类Unsafe,接下来开始证明我们的猜想。
//unsafe:表示对原子操作的封装类 private static final Unsafe unsafe = Unsafe.getUnsafe(); //被volatile修饰的变量 private volatile long value;
看到上面的两个成员变量,大家是不是找到了一点感觉,原来AtomicInteger是通过关键字volatile和CAS保证多线程下安全的修改一个共享变量。接下来我们继续往下分析。
//值value在内存的偏移地址
private static final long valueOffset;
//静态代码块就是获取这个偏移地址的。
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
上面的valueOffset就是变量value在内存的地址偏移量,也就是上一篇我介绍的CAS(V,E,N)中的V。
在AtomicInteger中还有一个成员变量,如下:
//表示当前虚拟机是否支持long类型无锁化CAS机制 static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); private static native boolean VMSupportsCS8();
上面把AtomicInteger的成员变量都介绍完了,通过被volatile修饰的value和unsafe封装的原子操作通过无锁化来保证对一个变量的线程安全,我们并没有看到加锁,说明AtomicInteger是一种乐观锁。
2.3、AtomicInteger的构造函数
//第一个构造函数:无参数构造
public AtomicLong() {
}
//第二个构造函数:传递一个初始化value值。
public AtomicLong(long initialValue) {
value = initialValue;
}
构造函数非常的简单,我们接下来继续分析它的一些重要的方法。
2.4、AtomicInteger的重要方法
AtomicInteger类中主要分为四类方法,分别如下:
第一类:对单一value的操作。 第二类:对value的复合操作 第三类:对value更加复杂的操作。 第四类:对Number方法的实现。
第一类:对value的简单操作
//对volatile变量value的写
public final void set(long newValue) {
value = newValue;
}
//对volatile变量value的读
public final long get() {
return value;
}
上面两个方法是对voaltile变量的写和读,大家还记得volatile的两个特性吗?
1:任意对volatile变量的写,JMM都会立刻将工作内存中的值刷新到主存中。 2:任意对volatile变量的读,JMM都会从主存中复制一份到自己的工作内存。
上面的两个方法是对被volatile修饰的单一变量的写和读,并没有任何的复合操作,所以它具有可见性、有序性和原子性,所以在多线程下是安全的。
第二类:对value的符合操作
上一篇文章我讲过关键字volatile只能保证可见性和有序性(禁止指令重排序),但是volatile不具备原子性的特点,所以对value的复合操作不能保证线程安全,所以需要在配合CAS原子操作才能保证多线程下对value修改的安全。
//这个方法就是我们所说的CAS
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
//通过CAS原子的设置value=newValue,然后返回旧值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//通过CAS原子的设置value+1,然后返回+1前的旧值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//通过CAS原子的设置value+delta,然后返回+delta前的旧值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//通过CAS原子的设置value+1,然后返回+1后的新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//通过CAS原子的设置value+delta,然后返回+delta后的新值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
//通过CAS原子的设置value-1,然后返回-1前的旧值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//通过CAS原子的设置value-1,然后返回-1后的新值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
上面的方法无非就是通过CAS操作+1或者-1,+delta或者-delta,要么返回旧值,要么返回新值,所以都是一些复合操作,上面的方法都是调用了Unsafe中的getAndInt()方法,那么我们继续跟进到Unsafe中看看这个方法。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
上面这个方法是不是非常的熟悉,通过CAS操作,成功则返回,失败则继续自旋。流程图如下:
上面的几个方法是对value的复合操作,不像第一类操作,能够直接通过关键字volatile就可以保证线程安全,而由于volatile不具备原子性,所以对value的符合操作需要通过volatile+CAS保证线程安全。
1:对value的单一操作:直接通过volatile保证线程安全。 2:对value的复合操作:通过volatile+CAS保证线程安全。
第三类操作:通过传递功能接口实现对value更复杂的操作
那什么是对value更复杂的操作呢?第二类复合操作只是在原来value的基础上加上一个值或者减去一个值,那么我们向在value的基础上乘上一个值或者除以一个值,或者更加复杂的运算,上面的方法就无法使用了,那么为了解决这个问题就出现了对value更加复杂的操作,例如:3*value+3这种操作,AtomicInteger就为我们提供了这些方法,通过传递一个功能接口,来实现我们想要的,而这个功能接口有一个方法需要我们实现,而复杂的运算就在这个方法中。首先我们看一下这个功能接口:
@FunctionalInterface
public interface IntUnaryOperator {
/**
* Applies this operator to the given operand.
*
* @param operand the operand
* @return the operator result
*/
int applyAsInt(int operand);
}
------------------------------
@FunctionalInterface
public interface IntBinaryOperator {
/**
* Applies this operator to the given operands.
*
* @param left the first operand
* @param right the second operand
* @return the operator result
*/
int applyAsInt(int left, int right);
}
功能接口一般会有FunctionalInterface注解,说明是一个功能接口,我们通过applyAsInt()方法实现我们的逻辑。那继续看AtomicInteger里面的方法:
//向功能接口方法传递value,并通过CAS+自旋原子操作,然后返回旧值
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
//向功能接口方法传递value,并通过CAS+自旋原子操作,然后返回新值
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
//向功能接口方法传递value和指定的x,并通过CAS+自旋原子操作,然后返回旧值
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
//向功能接口方法传递value和指定的x,并通过CAS+自旋原子操作,然后返回新值
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
有的同学还是不理解这个功能接口,我对这一类举一个例子:
public class AtomicIntegerTest1 {
private static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
//将原来值扩大到原来的2倍
ai.getAndUpdate(new IntUnaryOperator() {
@Override
public int applyAsInt(int operand) {
return operand * 2;
}
});
System.out.println("ai扩大为原来的2倍:" + ai.get());
//将原来的值扩大原来3倍,在减去指定的x
//left:就是value
//right:就是我们指定的10
ai.accumulateAndGet(10, new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return 3 * left - right;
}
});
System.out.println("ai扩大为原来的3倍,在减去指定的值:" + ai.get());
}
}
运行结果如下:
通过上面的运行结果,大家是不是理解了这一类方法的作用。
第四类操作:对Number方法的实现
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
这一类方法非常的简单了,都是调用get()获取value,然后转化成不同的基本类型。
上面就是AtomicInteger的全面内容了,通过上面的讲解,大家是否对AtomicInteger有了一个更加清晰的理解呢?如有什么不明白的地方,欢迎留言交流。如果对你有所帮助,请持续关注。
猜你喜欢
- 2024-10-20 使用idea将Java项目打成Jar包,之后生成exe可执行文件
- 2024-10-20 Java并发包,你需要掌握的atomic(java并发实现原理)
- 2024-10-20 一款易于使用的Java验证码软件包(javaee验证码)
- 2024-10-20 Java 覆盖 jar 包内的方法(java覆盖第三方jar中类文件)
- 2024-10-20 都说Java程序占内存多,那么Java对象究竟占多少内存?
- 2024-10-20 JAVA学习笔记之导包(java中的导包命令是哪个)
- 2024-10-20 简单介绍Java 的JAR包、EAR包、WAR包区别
- 2024-10-20 Java juc包学习笔记(java的juc包)
- 2024-10-20 Java带包结构调用命令行运行编译(java怎么使用包)
- 2024-10-20 package包和import导入的用法(package import作用)
欢迎 你 发表评论:
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)

本文暂时没有评论,来添加一个吧(●'◡'●)