JUC源码1-原子量
什么是原子量,原子量就是一次操作,要么成功,要么失败。比如java中的i++ 或i-- , 不具备原子性,每次读取的值都是不一样的,探究其原因,x86体系中,他的总线是32位的,i++的操作指令必须是分为2步实现,那是因为,为了确保原子性,jdk在atomic-AtomicXXX 类中,通过CAS来确保原子性。对原子量的读取可以读到最新,由volatile关键字来保证可见性。
对比一下下面的代码实现:
public class Incr {
public AtomicInteger a = new AtomicInteger(0);
public int incrAtomic(){
return a.getAndIncrement();
}
public int getAtomic(){
return a.get();
}
public int b = 0;
public int incrInt(){
return b++;
}
public int getInt(){
return b;
}
}
public class MultiThread {
private static Incr incr = new Incr();
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
incr.incrAtomic();
incr.incrInt();
}
}
}).start();
}
countDownLatch.countDown();
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AtomicInteger.incr: "+incr.getAtomic());
System.out.println("int.incr: " + incr.getInt());
}
}
接着,我们来看看,JUC中的atomicInteger类是如何实现CAS的?

查看源码,我们可以清晰的看到作者就是大名顶顶的 Doug Lee , 在代码的提示中,实现都是通过CAS(comparAndSwapInt)来实现数据的更新的,的注释归注释,但还是要看看,下面的具体实现,一看果然,有很多类似于下面的那样的代码,出现。

为了一探究竟,先去看看 unsafe 的实现。

查看Unsafe 类的时候,发现他的大多数实现,都是 native 属性,也就是说,他把实现都留在了JVM上实现,
虚拟机内存模型中定义的访问操作与物理计算机处理的基本一致!
Java中volatile关键字原义是“不稳定、变化”的意思 。使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。
其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我. 也就是MainMomery 中,而不是缓存中。这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。so , 有了volatile 并不意味着你万事大吉了,他也会很容易产生脏数据。
1:为什么会产生错误的数据?
多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。
2:为什么会造成同步问题?
Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。
3:为什么使用volatile修饰integer变量后,还是不行?
因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。
4:既然不能做到同步,那为什么还要用volatile这种修饰符?
主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。
5:那到底如何解决这样的问题?
第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。
第二种:采用原子性数据Atomic变量,这是从JDK1.5开始才存在的针对原子性的解决方案,这种方案也是目前比较好的解决方案了。(推荐,cas无锁操作。)
/**
直接设置volatile变量的值
*/
public final void set(int newValue) {
value = newValue;
} /**
putOrderedInt,去掉了storeLoad内存屏障,只保证最终设置成功,不保证多处理环境下,其他处理器read到最新的值
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
/**
loop循环,不断重试,直到成功
*/
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
/**
Atomic中n多方法通过loop来调用这个方法,类似乐观锁,expect表示期望的值,update是更新的值
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
代码跟compareAndSet没什么区别,
注释里面May fail spuriously and does not provide ordering guarantees,会导致伪失败,不保证指令有序
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
JUC源码1-原子量的更多相关文章
- 【JUC源码解析】ScheduledThreadPoolExecutor
简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...
- JUC源码分析-其它工具类(一)ThreadLocalRandom
JUC源码分析-其它工具类(一)ThreadLocalRandom ThreadLocalRandom 是 JDK7 在 JUC 包下新增的随机数生成器,它解决了 Random 在多线程下多个线程竞争 ...
- JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
- JUC源码分析-线程池篇(二)FutureTask
JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...
- JUC源码分析-线程池篇(三)Timer
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
- JUC源码分析-线程池篇(一):ThreadPoolExecutor
JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...
- JUC源码分析-集合篇:并发类容器介绍
JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...
- JUC源码分析-集合篇(十)LinkedTransferQueue
JUC源码分析-集合篇(十)LinkedTransferQueue LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,生产者会一直阻塞直到所添加到队列的元素 ...
- JUC源码分析-集合篇(九)SynchronousQueue
JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...
- JUC源码分析-集合篇(八)DelayQueue
JUC源码分析-集合篇(八)DelayQueue DelayQueue 是一个支持延时获取元素的无界阻塞队列.队列使用 PriorityQueue 来实现. 队列中的元素必须实现 Delayed 接口 ...
随机推荐
- SQL SERVER的锁机制(三)——概述(锁与事务隔离级别)
五.锁与事务隔离级别 事务隔离级别简单的说,就是当激活事务时,控制事务内因SQL语句产生的锁定需要保留多入,影响范围多大,以防止多人访问时,在事务内发生数据查询的错误.设置事务隔离级别将影响整条连接. ...
- 检查.net dll构建的目标平台是any cpu、x86、x64
有时候,需要检查构建的dll是否针对正确的平台 可以使用CorFlags.exe(它是.NET Framework SDK的一部分)从dll中查找此信息.运行CorFlags.exe将产生以下输出: ...
- 单片机编译器Keil提供的sprintf有点问题
AduC70xx系列,Keil提供的sprintf函数不太好用,好像有时会引起内存泄漏,造成不可预知的死机情况出现.
- Git安装学习记录
1.下载: https://gitforwindows.org/ 2.安装:https://blog.csdn.net/chengyuqiang/article/details/54178683 3. ...
- BZOJ 5194--[Usaco2018 Feb]Snow Boots(STL)
5194: [Usaco2018 Feb]Snow Boots Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 81 Solved: 61[Submi ...
- JavaScript基础(3)-JS中的面向对象、定时器、BOM、位置信息
一.创建对象的几种常用方式. 1.使用Object或对象字面量创建对象: a.使用Object()内置的构造函数来创建对象,例如: var student = new Object(); // 创建一 ...
- 2018宁夏邀请赛网赛 I. Reversion Count(java练习题)
题目链接 :https://nanti.jisuanke.com/t/26217 Description: There is a positive integer X, X's reversion c ...
- Linux巩固记录(5) hadoop 2.7.4下自己编译代码并运行MapReduce程序
程序代码为 ~\hadoop-2.7.4\share\hadoop\mapreduce\sources\hadoop-mapreduce-examples-2.7.4-sources\org\apac ...
- SQL 将一列多行数据合并为一行
原表数据: 期望结果: 使用STUFF + FOR XML PATH即可实现以上效果 执行以下SQL: , , '') AS Course FROM Student AS T 可以看到输出结果与期望结 ...
- 03-创建高可用 etcd 集群
本文档记录自己的学习历程! 创建高可用 etcd 集群 kuberntes 系统使用 etcd 存储所有数据,本文档介绍部署一个三节点高可用 etcd 集群的步骤,这三个节点使用以下机器: 192.1 ...