网站首页 > 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作用)
你 发表评论:
欢迎- 07-21如何将 iPhone 中的联系人导出到 Excel/CSV?
- 07-21sql查询的字段数据中有逗号。放到csv文件会分开,如何解决?
- 07-21在 WebAPI 里生成 csv zip 文件(webapi怎么发布在iis上)
- 07-21如何把csv格式转换成Excel格式(csv格式怎么转换)
- 07-21如何将高程导出为XYZ或CSV高程点(如何将高程数据导入cad)
- 07-21使用python把csv汇总成excel(python怎么将csv文件中的列存入列表)
- 07-21解决PHP导出CSV文件中文乱码问题(php导出excel文件)
- 07-21使用vba将Excel 文件转成 CSV 文件
- 最近发表
-
- 如何将 iPhone 中的联系人导出到 Excel/CSV?
- sql查询的字段数据中有逗号。放到csv文件会分开,如何解决?
- 在 WebAPI 里生成 csv zip 文件(webapi怎么发布在iis上)
- 如何把csv格式转换成Excel格式(csv格式怎么转换)
- 如何将高程导出为XYZ或CSV高程点(如何将高程数据导入cad)
- 使用python把csv汇总成excel(python怎么将csv文件中的列存入列表)
- 解决PHP导出CSV文件中文乱码问题(php导出excel文件)
- 使用vba将Excel 文件转成 CSV 文件
- python爬虫25 | 爬取的数据怎么保存?CSV了解一下
- MySQL 导出数据(mysql 导出数据 判断成功)
- 标签列表
-
- 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)
本文暂时没有评论,来添加一个吧(●'◡'●)