Java LongAdder 分段锁思想

embedded/2025/2/2 15:09:13/

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  1. 理解分段锁思想,了解LongAdder的原理

目录

  • LongAdder
    • 基本原理
    • 源码分析
      • unsafe int 操作的一些方法
      • Cell 对象(`Striped64`类下的静态内部类)
      • add(long x)
        • longAccumulate(x, null, uncontended)
      • sum()
      • `LongAdder`和`AtomicLong`

LongAdder

基本原理

LongAdder内部使用了分段锁Cell数组的机制来实现高效的并发累加。它将对一个变量的累加操作分散到多个Cell中,每个Cell维护一个独立的long值。在进行累加操作时,会根据一定的哈希算法将线程分配到不同的Cell上进行操作,这样可以减少多个线程同时访问同一个变量时的竞争,从而提高并发性能。

当多个线程并发地对LongAdder进行累加操作时,不同的线程可能会被分配到不同的Cell上进行操作,只有当多个线程同时访问同一个Cell时才需要进行同步操作。这种设计大大降低了锁的竞争程度,提高了并发性能。

在这里插入图片描述

java">/*** One or more variables that together maintain an initially zero* {@code long} sum.  When updates (method {@link #add}) are contended* across threads, the set of variables may grow dynamically to reduce* contention. Method {@link #sum} (or, equivalently, {@link* #longValue}) returns the current total combined across the* variables maintaining the sum.*
  • 只能做累加,或者自增自减操作,不能做其它操作
  • 用一个Cell数组来存放分段数据值大小,Cell数组元素只有一个volatile long value表示存放的值
  • sum方法用于返回当前计数值,返回所有Cell中value的和
  • 多个线程会进行hash,对不同的Cell元素进行操作
  • 内部有扩容方法,增加更多的Cell元素

英语单词:

  • probe 美[proʊb]: v. 盘问; 追问; 探究; (用细长工具) 探查,查看;n.探究; 详尽调查; (不载人) 航天探测器,宇宙探测航天器; (医生用的) 探针;
  • contention 美[kənˈtenʃn]: n. 争吵; 争执; 争论; (尤指争论时的) 看法,观点;

源码分析

unsafe int 操作的一些方法

  • public native int getInt(Object o, long offset);//获得给定对象偏移量上的int值
  • public native void putInt(Object o, long offset, int x);//设置给定对象偏移量上的int值
  • public native long objectFieldOffset(Field f);//获得字段在对象中的偏移量
  • public native void putIntVolatile(Object o, long offset, int x);//设置给定对象的int值,使用volatile语义
  • public native int getIntVolatile(Object o, long offset);//获得给定对象对象的int值,使用volatile语义
  • public native void putOrderedInt(Object o, long offset, int x);//和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的

Cell 对象(Striped64类下的静态内部类)

  • 使用了@sun.misc.Contended注解(缓存行使用,解决伪共享问题)
java">/*** Padded variant of AtomicLong supporting only raw accesses plus CAS.** JVM intrinsics note: It would be possible to use a release-only* form of CAS here, if it were provided.*/
@sun.misc.Contended static final class Cell {volatile long value;Cell(long x) { value = x; }final boolean cas(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);}// Unsafe mechanicsprivate static final sun.misc.Unsafe UNSAFE;private static final long valueOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> ak = Cell.class;valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value"));} catch (Exception e) {throw new Error(e);}}
}
  • Striped64静态初始化
java">// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long BASE;
private static final long CELLSBUSY;
private static final long PROBE;
static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> sk = Striped64.class;BASE = UNSAFE.objectFieldOffset(sk.getDeclaredField("base"));CELLSBUSY = UNSAFE.objectFieldOffset(sk.getDeclaredField("cellsBusy"));Class<?> tk = Thread.class;PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));} catch (Exception e) {throw new Error(e);}
}
  • base:为非竞争状态下的一个值
  • PROBE:一个随机的hashCode一样,每个线程有一个自己的probe,可以标识cells数组下标

add(long x)

java">/*** Adds the given value.** @param x the value to add*/
public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}
}
  • casBase就是一个CAS操作(一般自旋锁这里是whille(!cas){}),当有竞争的时候,CAS可能操作失败
java"> /*** CASes the base field.*/final boolean casBase(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);}
  1. as == null,
  2. (m = as.length - 1) < 0
  3. (a = as[getProbe() & m]) == null
  4. !(uncontended = a.cas(v = a.value, v + x))
  • if中判断1和判断2是判断cells是否为空
  • 判断3是判断当前线程的cell是否是null
  • 判断4是当前线程进行cas操作
  • 最后是longAccumulate(x, null, uncontended);
longAccumulate(x, null, uncontended)

如果Cell[]数组未初始化,会调用父类的longAccumelate去初始化Cell[],如果Cell[]已经初始化但是冲突发生在Cell单元内,则也调用父类的longAccumelate,此时可能就需要对Cell[]扩容了。

sum()

base + cells数组中各内容值

java">public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}

LongAdderAtomicLong

性能对比


http://www.ppmy.cn/embedded/158929.html

相关文章

shiro学习五:使用springboot整合shiro。在前面学习四的基础上,增加shiro的缓存机制,源码讲解:认证缓存、授权缓存。

文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication…

大模型应用的10个架构挑战

[引] 在英国&#xff0c;时差有点乱。拾起年初的文字&#xff0c;迎接新春大吉&#xff01; ChatGPT从正式发布到拥有1亿用户仅仅用了5天的时间&#xff0c;基于大型语言模型&#xff08;简称大模型&#xff0c;或基础模型&#xff09;的应用给软件行业乃至整个社会带来巨大的影…

Spring JDBC:简化数据库操作的利器

前言 Spring框架为Java开发者提供了多种技术解决方案&#xff0c;Spring JDBC作为其中的核心模块之一&#xff0c;帮助开发者更加轻松、简洁地进行数据库操作。本文将介绍Spring JDBC的概念、优势、如何使用以及常见的应用场景。 什么是Spring JDBC&#xff1f; Spring JDBC是…

数据库、数据仓库、数据湖有什么不同

数据库、数据仓库和数据湖是三种不同的数据存储和管理技术&#xff0c;它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别&#xff1a; 1. 数据结构与存储方式 数据库&#xff1a; 数据库主要用于存储结构化的数据&…

1.Template Method 模式

模式定义 定义一个操作中的算法的骨架&#xff08;稳定&#xff09;&#xff0c;而将一些步骤延迟&#xff08;变化)到子类中。Template Method 使得子类可以不改变&#xff08;复用&#xff09;一个算法的结构即可重定义&#xff08;override 重写&#xff09;该算法的某些特…

租房管理系统实现智能化租赁提升用户体验与运营效率

内容概要 在当今快速发展的租赁市场中&#xff0c;租房管理系统的智能化转型显得尤为重要。它不仅帮助房东和租客之间建立更高效的沟通桥梁&#xff0c;还优化了整个租赁流程。通过智能化技术&#xff0c;这套系统能够自动处理资产管理、合同签署、财务管理等所有关键环节。这…

验证二叉搜索数(98)

98. 验证二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* …

rust如何操作oracle

首先鄙视甲骨文&#xff0c;这么多钱的公司&#xff0c;不做一个rust库&#xff0c;还要社区帮忙。有个开源的rust库&#xff0c;叫oracle&#xff0c;但是并不是甲骨文做的。 我们来看一个从oracle数据库取所有表和视图的示例: // 定义连接字符串let conn_str1 format!(&quo…