首页 > 编程语言 >什么是原子操作?Java如何实现原子操作?

什么是原子操作?Java如何实现原子操作?

时间:2024-09-23 19:02:27浏览次数:13  
标签:缓存 Java CAS 原子 处理器 操作 CPU

1.什么是原子操作?

我们在学习MYSQL时就了解过原子性,即整个事务是不可分割的最小单位,事务中任何一个语句执行失败,所有已经执行成功的语句也要回滚,整个数据库状态要恢复到执行任务前的状态。Java中的原子性其实就是和数据库中说的相似,就是不可在分割,在我们的多线程里面就是相当于一把锁,在当前的线程没有完成对应的操作之前,别的线程不允许切换过来,那么Java中如何实现代码操作中的原子性?在说明这个问题之前,我们先来看一些术语,方便接下来的理解。

2.处理器如何实现操作的原子性?

处理器通常采用缓存加锁或者总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从内存中读取或者写入一个字节是原子的,意思是,当一个处理器读取一个字节时,其他处理器就不能访问这个字节的内存地址。Pentium6和最新的处理器可以保证单处理器对于同一个缓存进行的16/32/64位的操作是原子性的,但是复杂的内存操作处理器是不能自动保证其原子性的,比如跨总线宽度,跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定和缓存行锁定的两个操作来保证复杂内存操作的原子性。

2.1使用总线锁保证原子性:

如果多个处理器同时对共享变量进行改写(例如i++),那么共享变量就会被多个处理器同时进行操作,这样读写操作就不是原子的,操作完之后共享变量的值就会和期望值不一致。

原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入各自的内存中。那么要想保证读和写是原子性的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。处理器使用总线锁就是来解决这个问题的。所谓总线索就是使用处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器 的请求将被阻塞住,那么该处理器可以独占共享资源。

这里顺便说一下,JVM也就是Java的内存模型:

上图是传统的计算机架构,组成包括以下几个

(1)CPU

一般在大型服务器上会配置多个CPU,每个CPU还会有多个核,这就意味着多个CPU或者多个核可以同时(并发)工作。如果使用Java起了一个多线程任务,很有可能每个CPU都会跑一个线程,那么你的任务在某一时刻就是真正的并发执行了。

(2)CPU Register

CPU Register也就是CPU寄存器。CPU寄存器是CPU内部集成的,在寄存器上执行操作的效率要比在主存上高出几个数量级。

(3)CPU Cache Memory

CPU Cache Memory就是CPU缓存,相对于寄存器来说,通常也可以成为L2二级缓存。相对于硬盘读取速度来说内存读取的效率非常高,但是与CPU还是相差数量级,所以在CPU和主存之间引入了多级缓存,目的就是为了做一下缓冲。

(4)Main Memory

Main Memory就是主存。

2.2使用缓存锁保证原子性:

第二个机制就是使用缓存锁来保证原子性。在同一时刻,我们只需要对某个内存地址的操作是原子性即可,但总线锁把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁的开销较大,目前处理器在某些场合下适应缓存锁来代替总线锁进行优化。

频繁使用的内存会缓存在L1,L2,L3高速缓存中,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在Pentium6和目前的处理器中,可以使用”缓存锁定”的方式来实现复杂度原子性。所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存行中,并且在LOCK期间被锁定,那么当他执行所操作回写奥内存时,处理器不再总线上声明LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改两个以上处理器缓存的内存数据区域数据,当其他处理器回写已被修改缓存行的数据时,会使得缓存行无效。

但是有两种情况处理器不会使用缓存锁定:

  • 情况一:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,处理器会调用总线锁定。
  • 情况二:有些处理器不支持缓存锁定

3.Java如何实现原子操作?

在Java中可以通过锁和循环CAS的方式来实现原子操作。

3.1使用CAS实现原子操作

JVM中的CAS操作利用的是处理器提供的CMPXCHG指令实现。自旋CAS实现的基本思路就是循环进行CAS直到成功。举例:

package com.cl.pattern.cas;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @desc author Chen lei
 * @date 2024/9/22 18:29
 */
public class Counter {
    private AtomicInteger atomicInteger = new AtomicInteger(0);
    private int i = 0;
    public static void main(String[] args) {
        final Counter cas = new Counter();
        List<Thread> ts = new ArrayList<Thread>(600);
        long start = System.currentTimeMillis();
        for (int j = 0;j < 100;j++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0;i < 10000;i++){
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(t);
        }
        for (Thread t:ts){
            t.start();
        }
        for (Thread t : ts) {
            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(cas.i);
        System.out.println(cas.atomicInteger.get());
        System.out.println(System.currentTimeMillis() - start);
    }
    private void safeCount(){
        for (;;){
            int i = atomicInteger.get();
            boolean suc = atomicInteger.compareAndSet(i,++i);
            if (suc){
                break;
            }
        }
    }
    private void count(){
        i++;
    }
}

3.2CAS实现原子性操作的三大问题

  • ABA问题
  • 循环时间长,开销大
  • 只能保证一个共享变量的原子操作

3.2.1ABA问题:

因为CAS需要在操作值的时候,检查值有么有发生变化,如果没有发生变化则更新,但是如果一个值原来只是A,变成了B,又变成了A,那么使用CAS进行检查时就会发现它的值没有发生变化,但实际上发生变化了。ABA问题的解决思路就是使用版本号,每次变量更新时把版本号+1,那么A-B-A就会变成1A-2B-3A。从jdk1.5开始,JDK的Atomic包里就提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和标志位的值设定为给定的更新值。

3.2.2循环开销时间长问题:

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

3.2.3只能保证一个共享变量的原子操作:

对一个共享变量进行CAS操作时,我们可以使用循环CAS的方式来保证操作的原子性,但是多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,还有一个取巧的方法就是把多个共享变量合并成一个共享变量来进行操作。从 Java 1.5 开始, JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。

3.2使用锁机制实现原子操作

锁机制保证了只有获得锁的线程可以操作锁定的内存区域。JVM内部实现了很多锁机制,有偏向锁,轻量级锁和互斥锁。有意思的是,除了偏向锁,JVM实现锁的方式都是用来循环CAS,即当一个线程进入同步块时使用循环CAS的方式来获取锁,当他退出同步块时使用循环CAS释放锁。

标签:缓存,Java,CAS,原子,处理器,操作,CPU
From: https://www.cnblogs.com/chenlei210162701002/p/18427663

相关文章

  • JAVA基础之八-方法变量作用域和编译器
    本文主要讨论方法中变量作用域。不涉及类属性变量、静态变量、线程变量共享等。虽然知道某类变量的作用域非常重要,但是没有太多需要说的,因为许多东西是显而易见,不言自明。 在大部分情况下,或者在老一点版本中,java语法看起来都比较正常,或者说相对古典。但是随着JAVA版本的迭代,......
  • JavaScript 学习路线图
    基础阶段主要内容:掌握JavaScript的基本语法,如变量、数据类型(字符串、数字、布尔、对象、数组等)、运算符等。理解程序的控制流,包括条件语句(如if-else)、循环语句(如for、while)。学会使用函数来封装代码,理解函数的参数、返回值以及作用域等概念。学习网站:W3Schools:https://w......
  • java如何调用外部程序
    java如何调用外部程序2017-03-1520:50179人阅读评论(0)收藏举报分类:Java应用(26)版权声明:本文为博主原创文章,未经博主允许不得转载。引言;有时候有些项目需求,直接使用Java编写比较麻烦,所有我们可能使用其他语言编写的程序来实现。那么我们如何在java中......
  • oracle数据类型和对应的java类型
    [转]oracle数据类型和对应的java类型 地址:http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/java.102/B19275-03/datacc.htm#BHCJBJCCSQL数据类型JDBC类型代码标准的Java类型Oracle扩展的Java类型 1.0标准的JDBC类型:  CHARjava.......
  • 后台操作出错:索引中丢失 IN 或 OUT 参数:: 22
    简单记录下:今天mybatis中遇到一个错误:org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT INTO law_enforce_user(user_code,name,sex,birthday) VALUES(?,?,?,?)]; SQL state [99999];......
  • MySQL 增删操作面试题
    在数据库操作中,数据的增删是最基础也是最常见的操作。MySQL作为流行的关系型数据库,增删操作在面试中经常涉及。本文准备了30道关于MySQL增删操作的面试题,按照简单、中等、困难的难度划分,并提供了详细的答案和对应的SQL语句。通过这些问题,可以深入理解MySQL在实际应用中的增删操作。......
  • java 如何实现判断一个对象所有的属性是否为空
     能适配所有类型的:Personperson=newPerson();person.setId(0);//---truePersonperson=null;//---truePersonperson=newPerson();person.setName("xxx");//---falsePersonperson=newPers......
  • Javascript调试命令——你只会Console.log() ?
    Javascript调试命令——你只会Console.log()?https://segmentfault.com/a/1190000012957199Console对象提供对浏览器控制台的接入(如:Firefox的WebConsole)。不同浏览器上它的工作方式是不一样的,但这里会介绍一些大都会提供的接口特性。Console对象可以在任何全局对象中访问,......
  • JAVA Response 返回值再拿
    在Java中,当你需要从Response对象中获取返回值时,可以使用以下方法:首先,确保你已经导入了相关的库。例如,如果你使用的是java.net.HttpURLConnection,则需要导入以下包:importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.net.HttpURLConnection;importjav......
  • 《深入解析:水果销售数据库操作与查询技巧》
    文章目录一、数据库结构与数据源插入1.1创建数据库与表1.2插入数据二、基础数据查询2.1查询客户信息2.2查询供应商信息三、查询优化与技巧3.1使用LIMIT子句四、高级查询技巧4.1使用聚合函数4.2连接查询4.3使用子查询五、案例分析5.1客户订单详情查询一......