什么是原子量,原子量就是一次操作,要么成功,要么失败。比如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上实现,

在openJdk代码中可以找到这个类,目录openJdk的jdk/share/classes/sun/misc/。
       这个类里面大多数方法都是native的,方法实现可以在openJdk的hotspot/share/vm/prims/unsafe.cpp里面找到。
c的实现, 就是(多核下带lock前缀的)cmpxchgq命令了。putOrderedObject方法按之前几篇的查找方法,会发现内联之后,相当于一个普通写操作了。
具体可以参考这篇文章:http://brokendreams.iteye.com/blog/2250109
要看懂,必须了解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无锁操作。)

 就是(多核下带lock前缀的)cmpxchgq命令了。
       putOrderedObject方法按之前几篇的查找方法,会发现内联之后,相当于一个普通写操作了。
cas 代码中都会出现大量的:
for(;;){
}
操作!并意味者,他是在不断的请求锁。

/**
直接设置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-原子量的更多相关文章

  1. 【JUC源码解析】ScheduledThreadPoolExecutor

    简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...

  2. JUC源码分析-其它工具类(一)ThreadLocalRandom

    JUC源码分析-其它工具类(一)ThreadLocalRandom ThreadLocalRandom 是 JDK7 在 JUC 包下新增的随机数生成器,它解决了 Random 在多线程下多个线程竞争 ...

  3. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  4. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

  5. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

  6. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

  7. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

  8. JUC源码分析-集合篇(十)LinkedTransferQueue

    JUC源码分析-集合篇(十)LinkedTransferQueue LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,生产者会一直阻塞直到所添加到队列的元素 ...

  9. JUC源码分析-集合篇(九)SynchronousQueue

    JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...

  10. JUC源码分析-集合篇(八)DelayQueue

    JUC源码分析-集合篇(八)DelayQueue DelayQueue 是一个支持延时获取元素的无界阻塞队列.队列使用 PriorityQueue 来实现. 队列中的元素必须实现 Delayed 接口 ...

随机推荐

  1. [NewCoder 7] 用两个栈实现队列

    题目描述 用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 水题,直接上代码: class Solution { public: void push(int nod ...

  2. CAS Ticket票据:TGT、ST、PGT、PT、PGTIOU

    CAS的核心就是其Ticket,及其在Ticket之上的一系列处理操作.CAS的主要票据有TGT.ST.PGT.PGTIOU.PT,其中TGT.ST是CAS1.0协议中就有的票据,PGT.PGTIOU ...

  3. MAC系统下用Idea创建spring boot工程 基于maven

    1.创建项目 打开idea编辑器,选择file  -> new -> project 点击next 依次填入group,artifact 填写完成之后再点击“next” 根据自己的需求在最 ...

  4. VS的一些实用快捷键及小技巧(不断更新)

    在未选中文本的情况下: ctrl+x 剪贴并删除当前的行,可以用于快速删除整行代码 ctrl+c 复制当前行的代码 ctrl+l 删除当前行 组合键,需要按两次: ctrl+k,ctrl+c 注释当前 ...

  5. C#单例类的实现

    C#单例类的实现 单例类保证一个类全局仅有一个实例,并提供一个全局访问点,由于只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例. 实现1:懒汉式,线程不安全 该实现没有额外开销, ...

  6. Java 泛型通配符

    package com.waston; import java.util.*; public class Main { public static void main(String[] args) { ...

  7. 大脸猫讲逆向之ARM汇编中PC寄存器详解

    i春秋作家:v4ever 近日,在研究一些开源native层hook方案的实现方式,并据此对ARM汇编层中容易出问题的一些地方做了整理,以便后来人能有从中有所收获并应用于现实问题中.当然,文中许多介绍 ...

  8. linux apache+php+mysql安装及乱码解决办法

    1.乱码解决方法 首先确认mysql数据库字符集设置正确,php页面字符设置正确,之后修改apache配制文件http.conf 注释掉以下字符 AddDefaultCharset UTF-8 此为乱 ...

  9. Linq to xml修改CDATA节点值

    增加节点时,我们是这样写的: xop.Document.Element("messages").Add( new XElement("message", new ...

  10. 二维数组与类的定义_DAY06

    1:二维数组(理解): (1)格式:   1:int[][] arr = new int[3][2];  2:int[][] arr = new int[3][];   3:int[][] arr = ...