什么是原子操作

原子操作是指一个或者多个不可再分割的操作。这些操作的执行顺序不能被打乱,这些步骤也不可以被切割而只执行其中的一部分(不可中断性)。举个列子:

//就是一个原子操作
int i = 1; //非原子操作,i++是一个多步操作,而且是可以被中断的。
//i++可以被分割成3步,第一步读取i的值,第二步计算i+1;第三部将最终值赋值给i
i++;

Java中的原子操作

在Java中,我们可以通过同步锁或者CAS操作来实现原子操作。

CAS操作

CAS是Compare and swap的简称,这个操作是硬件级别的操作,在硬件层面保证了操作的原子性。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。Java中的sun.misc.Unsafe类提供了compareAndSwapIntcompareAndSwapLong等几个方法实现CAS。

另外,在jdk的atomic包下面提供了很多基于CAS实现的原子操作类,见下图:

下面我们就使用其中的AtomicInteger来看看怎么使用这些原子操作类。

package com.csx.demo.spring.boot.concurrent.atomic;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerDemo { private static int THREAD_COUNT = 100; public static void main(String[] args) throws InterruptedException { NormalCounter normalCounter = new NormalCounter("normalCounter",0);
SafeCounter safeCounter = new SafeCounter("safeCounter",0);
List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < THREAD_COUNT ; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
normalCounter.add(1);
safeCounter.add(1);
}
}
});
threadList.add(thread);
} for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
thread.join();
}
System.out.println("normalCounter:"+normalCounter.getCount());
System.out.println("safeCounter:"+safeCounter.getCount());
} public static class NormalCounter{
private String name;
private Integer count; public NormalCounter(String name, Integer count) {
this.name = name;
this.count = count;
} public void add(int delta){
this.count = count+delta;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getCount() {
return count;
} public void setCount(Integer count) {
this.count = count;
}
} public static class SafeCounter{
private String name;
private AtomicInteger count; public SafeCounter(String name, Integer count) {
this.name = name;
this.count = new AtomicInteger(count);
} public void add(int delta){
count.addAndGet(delta);
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getCount() {
return count.get();
} public void setCount(Integer count) {
this.count.set(count);
}
} }

上面的代码中,我们分别创建了一个普通的计数器和一个原子操作的计数器(使用AtomicInteger进行计数)。然后创建了100个线程,每个线程进行10000次计数。理论上线程执行完之后,计数器的值都是1000000,但是结果如下:

normalCounter:496527
safeCounter:1000000

每次执行,普通计数器的值都是不一样的,而使用AtomicInteger进行计数的计数器都是1000000。

CAS操作存在的问题

  • ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  • 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

  • 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

使用锁来保证原子操作

还是以上面的列子为列,普通的计数器我们只需要在计数方法上加锁就行了:

public synchronized void  add(int delta){
this.count = count+delta;
}

执行结果如下:

normalCounter:1000000
safeCounter:1000000

两个计数器都能拿到正确的结果

CPU是怎么实现原子操作的

【并发编程】Java中的原子操作的更多相关文章

  1. 【Java并发】Java中的原子操作类

    综述 JDK从1.5开始提供了java.util.concurrent.atomic包. 通过包中的原子操作类能够线程安全地更新一个变量. 包含4种类型的原子更新方式:基本类型.数组.引用.对象中字段 ...

  2. 读Java并发编程实践中,向已有线程安全类添加功能--客户端加锁实现示例

    在Java并发编程实践中4.4中提到向客户端加锁的方法.此为验证示例,写的不好,但可以看出结果来. package com.blackbread.test; import java.util.Arra ...

  3. Java并发编程-Java内存模型

    JVM内存结构与Java内存模型经常会混淆在一起,本文将对Java内存模型进行详细说明,并解释Java内存模型在线程通信方面起到的作用. 我们常说的JVM内存模式指的是JVM的内存分区:而Java内存 ...

  4. Java学习技术分享:Java中的原子操作

    学习java需要有一套完整的学习线路,需要有条理性,当下学习java已经有一段时间了,由当初的懵逼状态逐渐好转,也逐渐养成了写技术学习笔记的习惯,今天总结了一下java中的原子操作. 1.Java中的 ...

  5. Java中的原子操作

    Java中的原子操作 原子性:指该操作不能再继续划分为更小的操作. Java中的原子操作包括: 除long和double之外的基本类型的赋值操作 所有引用reference的赋值操作 java.con ...

  6. java中的原子操作类AtomicInteger及其实现原理

    /** * 一,AtomicInteger 是如何实现原子操作的呢? * * 我们先来看一下getAndIncrement的源代码: * public final int getAndIncremen ...

  7. 【多线程与并发】Java中的12个原子操作类

    从JDK1.5开始,Java提供了java.util.concurrent.atomic包,该包中的原子操作类提供了一种使用简单.性能高效(使用CAS操作,无需加锁).线程安全地更新一个变量的方式. ...

  8. Java并发编程(多线程)中的相关概念

    众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试中必问的题,一个好的Java程序员是必须对并发编程这块有所了解的. 并发必须知道的概念 在深入学习并发编程之前,我们需要了解几个基本 ...

  9. 什么是Java中的原子操作( atomic operations)

    1.啥是java的原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. 一个很经典的例子就是银行账户转账问题: 比如从账户A向账户B转1000元,那么 ...

随机推荐

  1. NDN helper 学习记录

    1.StackHelper 主要用于在请求的节点上安装ndnSIM网络堆栈, 提供一种简单的方法来配置NDN模拟的几个重要参数.(官方解释) 其实就是给结点装上堆栈 方法: 全部结点一次性安装(比较常 ...

  2. C# 结合 Golang 开发

    1. 实现方式与语法形式 基本方式:将 Go 程序编译成 DLL 供 C# 调用. 1.1 Go代码 注意:代码中 export 的注释是定义的入口描述不能省略 package main import ...

  3. 学习了解Shiro框架

    有关Shiro安全框架 实现权限的几种方式 1)通过表来实现 2)shiro框架 3)Spring Security框架 shiro有哪些主要功能 1.授权 访问控制的过程,即确定谁有权访问 2.身份 ...

  4. JVM(7) Java内存模型与线程

    衡量一个服务性能的高低好坏,每秒事务处理数(Transactions Per Second,TPS)是最重要的指标之一,它代表着一秒内服务端平均能响应的请求总数,而 TPS 值与程序的并发能力又有非常 ...

  5. 【ASP.NET Core学习】Razor页面

    准备工作 初始化空的项目(终端输入:dotnet new web -n=Razor) Nuget添加Microsoft.EntityFrameworkCore.SqlServer 和 Microsof ...

  6. 【Maven学习笔记】mvn help:system 命令的说明

    mvn help:system 命令的说明 笔者用得是windows 10 x64系统 下载了Maven3,正确配置了系统变量M2_HOME的值,并且添加到Path变量路径当中. 简单来说,Maven ...

  7. Mongo 导出为csv文件

    遇到需要从Mongo库导出到csv的情况,特此记录. 先贴上在mongo目录下命令行的语句: ./mongoexport -h 10.175.54.77 -u userName -p password ...

  8. MIT线性代数:12.图和网络

  9. C++等号操作符重载

    在新学操作符重载时最令人头疼的可能就是一些堆溢出的问题了,不过呢,只要一步步的写好new 与 delete.绝对不会有类似的问题. 当时我们编译可以通过,但是运行会出错,因为对象s1与s2进行赋值时, ...

  10. 实现ARM——Linux的自动登录

    在使用Linux系统嵌入式开发时,往往需要设备绕过Linux的登录系统使其自动启动,比如我们常用的SSH客户端等.网上确实有很多方法,不知道是因为我们的ARM9板子是私人订制的缘故还是什么原因,试了很 ...