J.U.C 系列之Atomic原子类
一 什么是原子类?
所谓原子类必然是具有原子性的类,原子性操作--原子操作,百度百科中给的定义如下
"原子操作(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原子类的更多相关文章
- JUC 中的 Atomic 原子类总结
1 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的.即使是 ...
- Atomic原子类
Atomic原子类 Atomic原子类位于并发包java.util.concurrent下的java.util.concurrent.Atomic中. 1. 原子更新基本类型类 使用原子方式更新基本数 ...
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ...
- Juc中Atomic原子类总结
1 Atomic原子类介绍 2 基本类型原子类 3 数组类型原子类 4 引用类型原子类 5 对象的属性修改类型原子类
- (转)Java atomic原子类的使用方法和原理(一)
在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicIntege ...
- Java线程--Atomic原子类使用
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871241.html Java线程--Atomic原子类使用 package concurr ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- [Java多线程]-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- JUC学习笔记--Atomic原子类
J.U.C 框架学习顺序 http://blog.csdn.net/chen7253886/article/details/52769111 Atomic 原子操作类包 Atomic包 主要是在多线程 ...
随机推荐
- Android 接入支付宝支付实现
接上篇android接入微信支付文章,这篇我们带你来接入支付宝支付服务 简介 首先要说明的是个人感觉接入支付宝比微信简单多了,很轻松的,所以同学们不要紧张~ 当然还是老规矩啦,上来肯定的贴上官网地址, ...
- mybatis由JDBC的演化过程分析
我们知道,mybatis是对JDBC的封装,那么他是如何演变过来的呢? 摘自传智传媒Java培训资料 关于mybatis的演化原理,我们先看看我们最熟悉也是最基础的通过JDBC查询数据库数据,一般需要 ...
- centos6.5_64bit-禅道安装及数据库操作
linux一键安装包内置了apache, php, mysql这些应用程序,只需要下载解压缩即可运行禅道. 从7.3版本开始,linux一键安装包分为32位和64位两个包,请大家根据操作系统的情况下载 ...
- Typora使用
Typora使用 1.介绍 typora是一款不错的软件. 1.1 基本使用 html js css python java vb 1.2 无序列表 a b c d e f 1.3 java代码 pu ...
- Element-ui(el-table、el-pagination)实现表格分页
HTML代码:(重点关注el-table中:data数据的绑定) el-pagination中: layout代表组件布局,子组件名用逗号分隔 属性: total代表总条目数 事件: current- ...
- 【转载】每天一个Linux命令
目 录 每天一个linux命令(1) : ls 命令 每天一个linux命令(2) : cd 命令 每天一个linux命令(3) : pwd 命令 每天一个linux命令(4) : mkdi ...
- API:相关词语笔记
1.SDK 软件开发套件,接口服务器把接口开发之后,把怎么使用的示范代码弄出来给API客户端的开发者参考. 2.头部信息 对头部信息的特殊符号有要求,例如: 持续更新中....
- 特殊矩阵的压缩存储(转自chunlanse2014)
对称矩阵 对于一个矩阵结构显然用一个二维数组来表示是非常恰当的,但在有些情况下,比如常见的一些特殊矩阵,如三角矩阵.对称矩阵.带状矩阵.稀疏矩阵等,从节约存储空间的角度考虑,这种存储是不太合适的.下面 ...
- JS回调函数(理解篇)
概述: 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而 ...
- 基于LBS的多人聊天