一 什么是原子类?

所谓原子类必然是具有原子性的类,原子性操作--原子操作,百度百科中给的定义如下

"原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1]  换到另一个线程)。

顾名思义,原子类就是一个一旦被执行就不能中断的类。

二 为什么需要原子类?

在看为什么需要原子类之前,我们看看普通Number类,在处理问题时可能存在的问题,这里我们通过Integer来演示

public class Main {

    private static Integer sum = 0;

    public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(new Task()).start();
} Thread.yield();
System.out.println(sum); } static class Task implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
sum++;
}
}
}
}

这段代码意思是开20个线程,每个线程对sum自加100次,理论上应该最后输出2000;但是事实上每次都是小于2000;

这是听说过volatile关键字的小伙伴可能会说,使用volatile来修饰sum,好,我们继续试验

public class Main {

    private static volatile Integer sum = 0;

    public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(new Task()).start();
} Thread.yield();
System.out.println(sum); } static class Task implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
sum++;
}
}
}
}

这是试验五次的输出

1766,1616,1859,1980,1800

还是都是小于2000,这是怎么回事,这里先提一下,volatile只能保证单个操作的原子性,而sum++,包括三个操作

sum = getSum()  //读取sum

temp = sum +1;  //sum+1,赋给临时变量

sum =  setSum(temp)      //将sum写回

因此,即使是volatile也无法保证sum++的原子性,volatile只能保证单个操作的原子性,而++操作是复合操作,volatile变量会在后续章节详细讨论;

那么,在Atomic未出现之前,是如何处理i++在多线程环境下的线程安全问题,主要是通过Synchronize加锁来处理,处理过程复杂,性能低

JDK5.0之后出现的Java.util.concurrent.Atomic包中为我们提供了13中原子类,来保证单个原子变量复合操作的原子性。下面我们通过AtomicInteger的使用来认识一下原子类

三 原子类示例详解

AtomicInteger 字段

   // setup to use Unsafe.compareAndSwapInt for updates
   //这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。
private static final Unsafe unsafe = Unsafe.getUnsafe();
   //valueOffset是用来记录value本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。
private static final long valueOffset;
   //value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。
private volatile int value;

AtomicInteger 构造方法

 /**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
} /**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}

AtomicInteger 并发安全实现

那么AtomicInteger是如何实现多线程的自增操作的线程安全的呢?核心思想就是CAS自旋;CAS:Compare And Swap 比较并交换。自旋:通过循环知道预期值和内存之相同,进行CAS操作,AtomicInteger的自增如下所示

       public final int incrementAndGet() {
for (;;) {
//这里可以拿到value的最新内存值
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
} public final boolean compareAndSet(int expect, int update) {
//使用unsafe的native方法,实现高效的硬件级别CAS
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

AtomicInteger 其他常用方法

  /**
* 返回旧值,然后自增1
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} /**
* 返回旧值,然后自减1
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
} /**
* 返回旧值,然后 旧值+delta
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
} /**
* 先进行自增,返回自增后的值
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
} /**
* 先进行自减,然后返回自减后的值
*
* @return the updated value
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
} /**
*先在原值上加delta,再返回加之后的值
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

注:其他Atomic类类似,不一一介绍;

四 Atomic 存在的问题

    》 长时间自旋,导致CPU和资源的占用

  》 只能保证单个原子变量的多线程安全操作,当然可以将多个变量封装成一个类,通过原子引用类型实现

  》 ABA问题;使用AtomicStampedReference 原子更新带有版本号的引用类型解决

J.U.C 系列之Atomic原子类的更多相关文章

  1. JUC 中的 Atomic 原子类总结

    1 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的.即使是 ...

  2. Atomic原子类

    Atomic原子类 Atomic原子类位于并发包java.util.concurrent下的java.util.concurrent.Atomic中. 1. 原子更新基本类型类 使用原子方式更新基本数 ...

  3. Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。

    精彩理解:  https://www.jianshu.com/p/21be831e851e ;  https://blog.csdn.net/heyutao007/article/details/19 ...

  4. Juc中Atomic原子类总结

    1 Atomic原子类介绍 2 基本类型原子类 3 数组类型原子类 4 引用类型原子类 5 对象的属性修改类型原子类

  5. (转)Java atomic原子类的使用方法和原理(一)

    在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicIntege ...

  6. Java线程--Atomic原子类使用

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871241.html Java线程--Atomic原子类使用 package concurr ...

  7. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  8. [Java多线程]-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  9. JUC学习笔记--Atomic原子类

    J.U.C 框架学习顺序 http://blog.csdn.net/chen7253886/article/details/52769111 Atomic 原子操作类包 Atomic包 主要是在多线程 ...

随机推荐

  1. oracle 递归查询(来源于网络)

    比如 a   b a   c   a   e b   b1 b   b2 c   c1 e   e1 e   e3 d   d1 指定parent=a,选出 a   b a   c   a   e b ...

  2. java日期时间Date类

    java.util包提供了Date类来封装当前的日期和时间. Date类提供两个构造函数来实例化Date对象. 第一个构造函数使用当前日期和时间来初始化对象. Date( ) 第二个构造函数接收一个参 ...

  3. 816 Ambiguous Coordinates (many cases problem)

    https://www.cnblogs.com/Java3y/p/8846955.html -- link of the problem 816 IDEA: check the dot and int ...

  4. css样式设置高度不定文本垂直居中

    使用css实现文本垂直居中,对于支持display: table的浏览器来说,是比较容易实现的,只需要对外层div设置为table,内层div设置为table-cell,并设置文本垂直居中即可.但对于 ...

  5. BZOJ 3233: [Ahoi2013]找硬币

    BZOJ 3233: [Ahoi2013]找硬币 标签(空格分隔): OI-BZOJ OI-DP Time Limit: 10 Sec Memory Limit: 64 MB Description ...

  6. Ubuntu 18.04 一键安装深度截图工具 Deepin Screenshot

    一直在寻找Linux下的截图软件,终于发现了Deepin ScreenShot,其功能齐全,界面美观,唯一的缺点需要自己配置快捷键(后面会讲). 安装 直接在Ubuntu商店搜索“深度截图”,点击“安 ...

  7. MapReduce执行jar练习

    1.用程序生成输入文件1.txt和2.txt 生成程序源码如下: https://www.cnblogs.com/jonban/p/10555364.html 2.  上传文件到hdfs文件系统 创建 ...

  8. idea中将项目转换成Maven项目

    第一步:项目右键->Add Framework... 选择maven ok 这样就成功转换成了一个maven项目

  9. p2597 灾难

    我的思路 代码: #include<cstdio> #include<iostream> #include<algorithm> #include<vecto ...

  10. 第5章 什么是寄存器—零死角玩转STM32-F429系列

    第5章     什么是寄存器 集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 本章参考资料 ...