首页 文章 精选 留言 我的

精选列表

搜索[编译原理],共10000篇文章
优秀的个人博客,低调大师

Java并发机制底层实现原理-原子操作的实现原理

章节目录 原子操作含义 相关术语 保证多处理器操作原子性的两种方式 Java语言层面上实现原子操作 原子操作的含义: 原子本意是"不能被进一步分割的最小粒子",而原子操作意为,不可中断的一个或一系列操作。 相关术语 术语名称 英文 解释 缓存行 Cache line 缓存的最小操作单位 比较并交换 Compare and Swap CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较旧值有没有发生变化,如果没有发生变化,才交换成新值,发生变化,表示有多线程竞争,则不交换 保证多处理器操作原子性的两种方式 通过总线锁保证操作共享变量是原子性的 如果多个处理器同事对共享变量进行读改写操作(i++是经典的读改写操作),那 么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子性的,操作完之后,共享变量的值会和期望的不一致。 如: public class IncreaceThread implements Runnable { public int i = 1; public void run() { this.i = ++i; } public int getI(){ return this.i; } public static void main(String[] args) { IncreaceThread increaseThread = new IncreaceThread(); Thread thread1 = new Thread(increaseThread); Thread thread2 = new Thread(increaseThread); thread1.start(); thread2.start(); System.out.println(increaseThread.getI()); } } 计算出最终i的值有可能是2,而不是3。 原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行+1操作,然后分别写入到系统内从中。 想要保证改写共享变量的操作是原子的,那就必须保证CPU1读改写共享变量的 时候,CPU2不能操作缓存了该共享变量内存地址的缓存。 处理器使用总线锁来解决这个问题,所谓总线锁,就是处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时, 其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。 使用缓存锁保证原子性 第二个机制是通过缓存锁定来保证原子性。 同一时刻,我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU 和内存之间的通信锁住,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁的开销比较大。 使用缓存锁定开销会变小,缓存锁定,是指内存区域如果被缓存在处理器缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性来保证操作的原子性。 Java语言层面上实现原子操作 在Java中通过锁和循环CAS的方式实现原子操作 使用循环CAS实现原子操作 JVM中的CAS操作正式利用了处理器提供的cmpxchg指令实现的,自旋基本思路就是循环进行CAS操作知道成功为止,以下代码实现可一个基于CAS线程安全的计数器方法safeCount 和一个非线程安全的计数器count。 package com.imooc.item; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger atomicInteger = new AtomicInteger(0); private int i = 0; public void safeCount() { for (; ; ) { int i = atomicInteger.get(); boolean suc = atomicInteger.compareAndSet(i, ++i); if (suc) { break; } } } //线程计数器 public void count() { i++; } public static void main(String[] args) { final Counter cas = new Counter(); List<Thread> ts = new ArrayList<Thread>(600); long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { 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) { e.printStackTrace(); } } System.out.println(cas.i); System.out.println(cas.atomicInteger.get()); System.out.println(System.currentTimeMillis() - start); } } 运行结果如下所示: 996309 1000000 61 CAS实现原子操作的三大问题 ABA 问题 因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则 更新,但如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会 发现它的值没有发生变化,但实际上却变了,ABA问题的解决思路就是使用版 本号,在变量前面加上版本号,每次变量更新的时候把版本号加1。 那么A->B->A问题就会变成1A-2B-3A. 循环时间开销大 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。常用做法是控制自旋的次数。 只能保证一个共享变量的原子操作 当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作, 但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可 以用锁。 使用锁机制实现原子操作 锁机制保证了只有获得锁的线程才能够 操作锁定的线程共享区域(临界区), JVM内部实现了很多的锁机制:偏向锁、轻量级锁和互斥锁。其中,除了偏向 锁,JVM实现锁的方式都使用了循环CAS,即当一个线程想进入同步块的时候使 用循环CAS的方式获取锁,当它退出同步块的时候使用循环CAS释放锁。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册