共享模型之无锁(三)

news2024/9/30 7:28:49

1.原子累加器

示例代码:

public class TestAtomicAdder {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            demo(
                () -> new AtomicLong(0),
                (adder) -> adder.getAndIncrement()
            );
        }

        for (int i = 0; i < 5; i++) {
            demo(
                () -> new LongAdder(),
                (adder) -> adder.increment()
            );
        }
    }

    /**
     * @param adderSupplier 提供者 无中生有 ()->结果
     * @param action        消费者 一个参数没结果
     * @param <T>
     */
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();

        long start = System.nanoTime();

        List<Thread> ts = new ArrayList<>();

        //4个线程,每个累加5万次,最终结果是200000
        for (int i = 0; i < 4; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 50000; j++) {
                    action.accept(adder);
                }
            }));
        }

        ts.forEach(t -> t.start());

        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(adder + " cost(ns):" + (end - start) / 1000_000);
    }
}

比较 AtomicLong 与 LongAdder:
在这里插入图片描述

LongAdder累加器性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0累加Cell[0],而Thread-1累加Cell[1]… 最后将结果汇总.这样它们在累加时操作不同的Cell变量,因此减少了CAS重试失败,从而提高性能;

1.1.LongAdder源码分析

1>.LongAdder是并发大师Doug Lea(大哥李)的作品,设计的非常精巧;

2>.LongAdder类有几个关键域:

// 累加单元数组,懒惰初始化
// transient关键字作用在是序列化的时候保证这些域不会被序列化
transient volatile Cell[] cells;

// 基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;

// 在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;

3>.CAS锁

// 不要用于实践!!!
public class LockCas {
   private AtomicInteger state = new AtomicInteger(0);
   public void lock() {
     while (true) {
       if (state.compareAndSet(0, 1)) {
          break;
        }
     }
   }

 public void unlock() {
    log.debug("unlock...");
    state.set(0);
 }
}

//测试代码
LockCas lock = new LockCas();
new Thread(() -> {
 log.debug("begin...");
 lock.lock();
 try {
   log.debug("lock...");
   sleep(1);
 } finally {
   lock.unlock();
 }
}).start();

new Thread(() -> {
 log.debug("begin...");
 lock.lock();
 try {
   log.debug("lock...");
 } finally {
   lock.unlock();
 }
}).start();

4>.缓存行(hang)伪共享(一个缓存行加入了多个内存cell对象被称为伪共享)
其中Cell即为累加单元

// 防止缓存行伪共享,防止一个缓存行容纳多个内存cell对象
@sun.misc.Contended
static final class Cell {
   volatile long value;
   Cell(long x) { value = x; }
 
   // 最重要的方法, 用cas方式进行累加, prev表示旧值, next表示新值
   final boolean cas(long prev, long next) {
      return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
   }
   // 省略不重要代码
}

1.1.1.LongAdder#add()源码

public void add(long x) {
    // as为累加单元数组
    // b为基础值
    // x为累加值
    Cell[] as; long b, v; int m; Cell a;

    // 进入 if 的两个条件
    // 1.as有值,表示已经发生过竞争, 进入if
    // 2.cas给base累加时失败了,表示base发生了竞争,进入if
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // uncontended表示cell没有竞争
        boolean uncontended = true;
           // as还没有创建
           // 当前线程对应的cell还没有
           // cas给当前线程的cell累加失败uncontended=false(a为当前线程的cell)
           if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                // 进入cell数组创建、cell创建的流程
                longAccumulate(x, null, uncontended);
     }
}

在这里插入图片描述

1.1.2.Striped64#longAccumulate()源码

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {
        int h;
        // 当前线程还没有对应的cell,需要随机生成一个h值用来将当前线程绑定到cell
        if ((h = getProbe()) == 0) {
            // 初始化probe
            ThreadLocalRandom.current(); // force initialization
            // h对应新的probe值,用来对应(/占用)cell
h = getProbe();
            wasUncontended = true;
        }
        // collide为true表示需要扩容
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            // 已经有了cells
            if ((as = cells) != null && (n = as.length) > 0) {
                // 还没有cell
                if ((a = as[(n - 1) & h]) == null) {
                    // 为cellsBusy加锁,创建cell,cell的初始累加值为x
                    // 成功则 break,否则继续continue循环
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                // 有竞争,改变线程对应的cell来重试cas
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // cas尝试累加,fn配合LongAccumulator不为null,配合LongAdder为null
else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                // 如果cells长度已经超过了最大长度,或者已经扩容,改变线程对应的cell来重试cas
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                // 确保collide为false进入此分支,就不会进入下面的else if进行扩容了
else if (!collide)
                    collide = true;
                // 加锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    // 加锁成功,扩容
                    continue;                   // Retry with expanded table
                }
                // 改变线程对应的cell对象
                h = advanceProbe(h);
            }
            // 还没有cells,尝试给cellsBusy加锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                // 加锁成功,初始化cells,最开始长度为2,并填充一个cell
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    // 成功则break;
                    break;
            }
            // 上两种情况失败,尝试给base累加
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
}

在这里插入图片描述在这里插入图片描述
每个线程刚进入longAccumulate()时,会尝试对应(/占用)一个cell对象(找到一个位置)
在这里插入图片描述

1.1.3.LongAdder#sum()源码

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)
            //将Cells数组中的每个元素进行累加
            sum += a.value;
      }
   }
   return sum;
}

扩展: 缓存与内存的速度比较

1>.CPU内存结构:
在这里插入图片描述
2>.CPU访问不同的缓存和内存耗费的时钟周期
在这里插入图片描述

①.CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率;
②.缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64byte(8个long);
③.缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中;
④.CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的整个缓存行(64byte)必须全部失效;
在这里插入图片描述
⑤.由于内存Cell是数组形式,在内存中是连续存储的,一个内存Cell为24字节(16字节的对象头和8字节的value),因此缓存行可以存下2个内存Cell对象.这样问题来了:

  • Core-0要修改Cell[0]
  • Core-1要修改Cell[1]

无论谁修改成功,都会导致对方Core的缓存行失效,比如Core-0中Cell[0]=6000,Cell[1]=8000要累加Cell[0]=6001,Cell[1]=8000,这时会让Core-1的缓存行失效;
⑥."@sun.misc.Contended"就是用来解决这个问题的,它的原理是在使用此注解的对象或字段的前后各增加128字节大小的padding(填充内存Cell),从而让CPU将(不同的)对象预读至缓存时占用不同的缓存行,这样不会造成对方缓存行的失效;
在这里插入图片描述

2.Unsafe对象

2.1.概述

1> Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe对象不能直接调用,只能通过反射获得;

2>.示例代码:

public class TestUnsafeDemo1 {
    public static void main(String[] args) throws Exception {
        //通过反射获取到unsafe对象,这里的参数"theUnsafe"是固定的写法,对应着unsafe对象中的(类类型)成员变量
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        //由于unsafe对象中"theUnsafe"成员变量是"private"私有的,因此要设置访问权限
        theUnsafe.setAccessible(true);
        //通过成员变量获取到所属的unsafe对象
        //由于unsafe对象中"theUnsafe"成员变量是静态的,因此这里原本的对象参数就是null
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        System.out.println(unsafe);  //sun.misc.Unsafe@7ea987ac
    }
}

3>.封装成工具类:

public class UnsafeAccessor {
    static Unsafe unsafe;
    static {
    try { 
           Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
           theUnsafe.setAccessible(true);
           unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
           throw new Error(e);
        }
    }
    static Unsafe getUnsafe() {
     return unsafe;
    }
}

2.2.Unsafe CAS操作

1>.使用Unsafe对象通过CAS机制线程安全的修改对象的成员变量:

@Slf4j
public class TestUnsafeCasDemo1 {
    public static void main(String[] args) throws Exception {
        //获取unsafe对象
        Unsafe unsafe = UnsafeAccessor.getUnsafe();
        //获取对象中某个成员变量的偏移地址
        long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
        //执行CAS操作
        Teacher teacher = new Teacher();
        boolean b = unsafe.compareAndSwapInt(teacher, idOffset, 0, 1);
        boolean b1 = unsafe.compareAndSwapObject(teacher, nameOffset, null, "张三");

        //打印结果
        log.info(teacher.toString());
    }
}

@Data
@NoArgsConstructor
class Teacher {
    volatile int id;
    volatile String name;
}

//unsafe对象封装
class UnsafeAccessor {
    static Unsafe unsafe;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }

    static Unsafe getUnsafe() {
        return unsafe;
    }
}

在这里插入图片描述

2.3.模拟实现原子整数

1>.以转账为例:

public class TestCustomAtomicIntegerDemo1 {
    public static void main(String[] args) {
        Account1.demo(new MyAtomicInteger(200));
    }
}

//自定义的原子整数类
class MyAtomicInteger implements Account1 {

    private volatile int value;

    private static final long valueOffset;
    private static final Unsafe UNSAFE;

    static {
        UNSAFE = UnsafeAccessorUtil.getUnsafe();
        try {
            valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

    public void decrement(int amount) {
        while (true) {
            int prev = this.value;
            int next = prev - amount;

            if (UNSAFE.compareAndSwapInt(this, valueOffset, prev, next)) {
                break;
            }
        }
    }

    @Override
    public Integer getBalance() {
        return this.getValue();
    }

    @Override
    public void withDraw(int amount) {
        this.decrement(amount);
    }
}

//unsafe工具类
class UnsafeAccessorUtil {
    static Unsafe unsafe;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }

    static Unsafe getUnsafe() {
        return unsafe;
    }
}

//封装转账相关的方法
interface Account1 {

    //获取余额
    Integer getBalance();

    //取款
    void withDraw(int amount);

    /**
     * 方法内会启动20个线程,每个线程做(-10元)的操作
     * 如果初始余额为200那么正确的结果应当是 0
     */
    static void demo(Account1 account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 20; i++) {
            ts.add(new Thread(() -> {
                account.withDraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
            + " cost: " + (end - start) / 1000_000 + " ms");
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++复习笔记6

1.String类的实现 注意深浅拷贝&#xff0c; C语言字符串拼接函数strcat() #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vld.h> #include<assert.h> using namespace std;class String {friend ostream& operator<<(ostream &am…

【DSView逻辑分析抓取波形CAN步骤-硬件连接-数据解析-底层波形认识CAN-工具使用】

【DSView逻辑分析抓取波形CAN步骤-硬件连接-数据解析-底层波形认识CAN】1、概述2、实验环境3、写在前面的一个问题4、实验准备&#xff08;1&#xff09;硬件连接1&#xff09;CAN卡连接开发板&#xff08;2&#xff09;逻辑分析仪连接开发板&#xff08;2) CAN卡连接软件&…

Linux 文件锁 - fcntl

什么是文件锁&#xff1f; 即锁住文件&#xff0c;不让其他程序对文件做修改&#xff01; 为什么要锁住文件&#xff1f; 案例&#xff0c;有两个程序&#xff0c;都对一个文件做写入操作。 #include <unistd.h> #include <stdio.h> #include <stdlib.h> …

【集群】Slurm作业调度系统的使用

最近使用集群进行实验&#xff0c;记录并学习集群系统进行深度学习的实验过程。集群所使用的作业调度系统为Slurm&#xff0c;这里记录下使用的常用命令和一些注意事项。 Slurm简介 Slurm是一个开源&#xff0c;容错&#xff0c;高度可扩展的集群管理和作业调度系统&#xff0…

excel数据处理: 如何用99个空格提取单元格数据

脑洞大开&#xff0c;提取单元格数据用99个空格就成&#xff01;真想扒开那些大神的脑袋看看&#xff0c;是怎么想出这样匪夷所思的方法的。需要从规格型号中提取容值、封装、耐压三组数据&#xff0c;如下&#xff1a;数据源在A列&#xff0c;数据量很大&#xff0c;需要提取的…

微信小程序Springboot短视频分享系统

3.1小程序端 用户注册页面&#xff0c;输入用户的个人信息点击注册即可。 注册完成后会返回到登录页面&#xff0c;用户输入自己注册的账号密码即可登录成功 登录成功后我们可以看到有相关的视频还有视频信息&#xff0c;我的信息等。 视频信息推荐是按照点击次数进行推荐的&am…

Zabbix 构建监控告警平台(四)

Zabbix ActionZabbix Macros1.Zabbix Action 1.1动作Action简介 当某个触发器状态发生改变(如Problem、OK)&#xff0c;可以采取相应的动作&#xff0c;如&#xff1a; 执行远程命令 邮件&#xff0c;短信&#xff0c;微信告警,电话 1.2告警实验简介 1. 创建告警media type&…

9.语义HTMLVScode扩展推荐

语义HTML 定义&#xff1a; 一个元素使用我们并不是只关心他是什么样子的&#xff0c;而是要去关心这个元素名称的实际意义或者代表什么 我们使用标签并不是他仅代表导航栏&#xff0c;只是将导航栏部分归为一个块。现实生活中&#xff0c;多使用之前都是使用div这个元素去构…

删除有序数组中的重复项-力扣26-java

一、题目描述给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度&#xff0c;所以必须将结果放在数组nums…

软件设计(九)

软件设计&#xff08;八&#xff09;https://blog.csdn.net/ke1ying/article/details/128954569?spm1001.2014.3001.5501 81、模块A将学生信息&#xff0c;即学生姓名、学号、手机等放到一个结构体系中&#xff0c;传递给模块B&#xff0c;模块A和B之间的耦合类型为 什么耦合…

【C++设计模式】学习笔记(1):面向对象设计原则

目录 简介面向对象设计原则(1)依赖倒置原则(DIP)(2)开放封闭原则(OCP)(3)单一职责原则(SRP)(4)Liskov替换原则(LSP)(5)接口隔离原则(ISP)(6)优先使用对象组合,而不是类继承(7)封装变化点(8)针对接口编程,而不是针对实现编程结语简介 Hello! 非常感谢您阅读海…

变分自编码器背后的直觉【VAE】

在阅读有关机器学习的内容时&#xff0c;你遇到的大部分材料可能都与分类问题有关。 你有一个特定的输入&#xff0c;ML 模型试图找出该输入的特征。 例如&#xff0c;分类模型可以决定图像中是否包含猫。 当你想创建具有预定义特征的数据时&#xff0c;反过来又如何呢&#x…

再不跳槽,就晚了

从时间节点上来看&#xff0c;3月、4月是每年跳槽的黄金季&#xff01; 以 BAT 为代表的互联网大厂&#xff0c;无论是薪资待遇、还是平台和福利&#xff0c;都一直是求职者眼中的香饽饽&#xff0c;“大厂经历” 在国内就业环境中无异于一块金子招牌。在这金三银四的时间里&a…

预处理指令详解

预处理指令详解**1.预定义符号****2.#define****2.1 #define 定义标识符****2.2 #define 定义宏****2.3 #define 替换规则****2.4 #和##****#的作用****##的作用****2.5 带副作用的宏参数****2.6 宏和函数的对比****宏和函数对比图****2.7 命名约定****3.#undef**4.条件编译4.1…

Leg转Goh引擎和架设单机+配置登陆器教程

教程准备1、Leg版本一个2、Goh引擎一套3、电脑一台(最好联网)前言&#xff1a;BLUE/LEGS/Gob/Goh/九龍、4K、AspM2第一步&#xff1a;更换引擎1、把版本自带的LEG引擎换成Goh引擎2、删除服务端里面的exe、dll文件(也可以直接更新)3、清理登录和游戏网关里面的配置文件4、更新引…

Sandman:一款基于NTP协议的红队后门研究工具

关于Sandman Sandman是一款基于NTP的强大后门工具&#xff0c;该工具可以帮助广大研究人员在一个安全增强型网络系统中执行红队任务。 Sandman可以充当Stager使用&#xff0c;该工具利用了NTP&#xff08;一个用于计算机时间/日期同步协议&#xff09;从预定义的服务器获取并…

菌子导航系统(持续开发中)

文章目录菌子导航前言项目架构spring-cloud 和 spring-boot 版本选择使用到的组件&#xff08;依赖&#xff09;架构分层项目基本功能1 使用Nacos做配置中心2 logback日志3 mybatis-plus操作数据库4 Caffeine 缓存整合5 LocalDateTime 序列化&反序列化6 参数校验快速失败配…

ubuntu20.04 系统下 .7z 文件解压缩到指定的目录下

问题描述 环境&#xff1a; ubuntu 20.04 ubuntu 下有个 7z 的压缩文件需要解压&#xff0c;需要解压到指定的目录下&#xff0c;而不是压缩包当前目录下 安装 p7zip-full ubuntu 下的 7z 解压软件&#xff1a; p7zip-full 安装命令&#xff1a; sudo apt install p7zip-fu…

04-PS人像磨皮方法

1.高斯模糊磨皮 这种方法的原理就是建立一个将原图高斯模糊后图层, 然后用蒙版加画笔或者历史画笔工具将需要磨皮的地方涂抹出来, 通过图层透明度, 画笔流量等参数来控制磨皮程度 1.新建图层(命名为了高斯模糊磨皮), 混合模式设置为正常, 然后选择高斯模糊, 模糊数值设置到看…

前端也能悄悄对视频截图?js实现对视频按帧缓存

前言 虽然最后没有采用这种方案来实现滚动控制视频进度&#xff0c;但是仍然想自己试试这种方案的实现&#xff0c;毕竟应用范围也挺广的。 核心代码并不多&#xff0c;算是一篇小短文&#xff5e;。 掘金好像不允许放站外演示链接&#xff0c;所以这里就用动图大概展示下最终…