CAS + 自旋 锁底层

news2024/11/15 11:42:10

多线程安全问题

  1. 为什么会出现多线程安全问题?
    在多线程并发下, 假设有 A,B 两个线程同时操作 count = 0 这个公共变量, 在A线程中count++, 在B线程中count++, 正常来说结果应该是 count = 2, 可是同时在A, B两个线程中拿到 count = 0 , 并且都执行count++赋值, 结果就变成了 count = 1
  2. 解决办法?
    • 原子操作类: AtomicLong, AtomicInteger, LongAdder(java8以上并发量大的情况下推荐)
    • 锁: Lock: ReentrantLock , synchronized

1 Volatile关键字

解决多线程内存不可见问题, 对于一写多读可以解决变量同步问题, 多些则无法解决线程安全问题.

2 CAS + 原子操作类

2.1 CAS是什么?

CAS是英文单词Compare And Swap的缩写,翻译过来就是`比较并替换`。
CAS性能优势分析:
	主频 1GHz 的cpu: 2-3纳秒
	一次上下文切换耗时2-8微妙
	
	1秒 = 1000毫秒
	1毫秒 = 1000微秒
	1微秒 = 1000纳秒	

2.2 AtomicLong实现

// 创建一个对象
AtomicLong atomicLong = new AtomicLong();
// 递增并获取最新的
long l = atomicLong.incrementAndGet();


public class AtomicLong extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
	        // 获取一块内存地址的偏移量, 在内存地址的编号
            valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

	// 调用的是以下这个方法
	public final long incrementAndGet() {
		// 自身, 偏移量, 1L
	   return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
	}
}

// AtomicLong, 偏移量, 1L
public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
	    // 拿到内存中的最新值
        var6 = this.getLongVolatile(var1, var2);
       // 计算 var1(AtomicLong)在内存 var2(valueOffset)中的结果 是否等于old(var6)值
       //  == 返回var6
       // !== 赋值var6+var4, 自旋 重新获取var6
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); // 这个是调用的c++然后使用汇编进行处理的, C++在多核cpu中也进行了锁处理
    return var6;
}

2.3 ABA问题

  1. ABA问题是什么?

    ABA问题是三种状态, 在第一次获取时对象的属性值为A, 这时被其他线程进行修改成B, 又被修改成A, 在第二次判断比较的时候看到还是A, 所以就进行了计算, 可是实际上已经被修改过了, 在int, 等基础类型中,没有问题, 可是在对象Object中会出现问题.

    // 创建string类型的CAS对象
    static AtomicReference<String> accessString  = new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
       new Thread(()->{
           // 是A的话更新成B
           boolean b1 = accessString.compareAndSet("A", "B");
           // 是B的话更新成A
           boolean b2 = accessString.compareAndSet("B", "A");
           System.err.println("ABA问题 = "+(b1&b2));
       }).start();
       
       Thread.sleep(1000);
       
       new Thread(()->{
           boolean b3 = accessString.compareAndSet("A","C");
           System.err.println("CAS结果 = " + b3);
       }).start();
    }
    

    结果:
    ABA问题 = true
    CAS结果 = true

  2. 解决方法

    使用乐观锁机制, (时间戳, 版本号) + 自旋

    // 创建包含记录的对象
    static AtomicStampedReference<String> accessStampString  = new AtomicStampedReference<>("A", 0);
    public static void main(String[] args) throws InterruptedException {
        int stamp = accessStampString.getStamp();
        new Thread(()->{
            // 是A的话更新成B, 判断时间戳是否是之前的, 是的话+1
            boolean b1 = accessStampString.compareAndSet("A", "B", accessStampString.getStamp(), accessStampString.getStamp()+1);
            // 是B的话更新成A, 判断时间戳是否是之前的, 是的话+1
            boolean b2 = accessStampString.compareAndSet("B", "A", accessStampString.getStamp(), accessStampString.getStamp()+1);
            System.err.println("ABA问题 = "+(b1&b2));
        }).start();
        Thread.sleep(1000);
    
        new Thread(()->{
            boolean b3 = accessStampString.compareAndSet("A","C", stamp, stamp+1);
            System.err.println("CAS结果 = " + b3);
        }).start();
    }
    
    

    结果:
    ABA问题 = true
    CAS结果 = false

3 AtomicLong 和 LongAdder

AtomicLong(1.8之前) 和 LongAdder(1.8及以后) 都能解决多线程问题, 那么我们用哪个?

  1. AtomicLong 底层中, 是对一个base对象进行操作, 多线程中, 第一个写入, 第二个第三个写入失败则CAS重试,导致CPU开销高
  2. LongAdder 底层中, 如果是单线程,则跟AtomicLong没有太大区别, 对base值进行计算, 如果是多线程环境则实行分片机制, 多线程进行写入时, 默认会创建两个cell进行计算, 每个cell分一些线程进入, 如果线程写入失败, 则创建更多的cell, 最后使用sum() = base + cell[0] + cell[1]+... 方法进行计算

在线程数量少的时候, 使用AtomicLong的时间更快, 在线程量多的话, 使用LongAdder更快, 线程数量越多,两个的差别越大

在这里插入图片描述

4 ReentrantLock 可重入锁 与 Synchronized

    static Lock lock = new ReentrantLock();
    int count = 0;
    public static void main(String[] args) {
        lock.lock();  //加锁
        try {
            // 业务代码
			count++;
        }finally {
            lock.unlock(); // 释放锁
        }
    }

4.1 手写MyLock

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author xyy
 * @DateTime 2023/7/3 13:20
 * @ClassName MyLock
 */
public class MyLock implements Lock {
    /**
     * 实现CAS比较对象
     * 锁的持有者
     */
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    /**
     * 阻塞队列
      */
    LinkedBlockingQueue<Thread> linkedBlockingQueue = new LinkedBlockingQueue<>();
    @Override
    public void lock() {
        // 如果里面是空则成功写入
        while (!atomicReference.compareAndSet(null, Thread.currentThread())){
            // 有锁被占用, 写入阻塞队列
            linkedBlockingQueue.add(Thread.currentThread());
            //让当前线程阻塞, 相当于打上断点!!!打断点!!!调试的时候的断点!!!
            // 在lock时进行停车阻塞, 在unlock时继续执行这里的代码!!!
            LockSupport.park();
            // 删除阻塞队列中的本线程, 重新竞争lock, 必须要删除!!!否则容易内存泄漏
            linkedBlockingQueue.remove(Thread.currentThread());
        }
    }

    @Override
    public void unlock() {
        // 只有当前锁的线程才能释放锁(CAS)
        if(atomicReference.compareAndSet(Thread.currentThread(), null)){
            // 将阻塞队列中的数据进行开放
            for (Thread thread : linkedBlockingQueue) {
                // unpark方法不一定能唤醒, 所以从这里删除阻塞队列不行
                LockSupport.unpark(thread);
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        return false;
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
}

4.2 synchronized

在 jdk1.6之前 synchronized是一把重量级锁, 跟 4.1 一样的,(在线程的上下文切换中非常耗时, 线程A上文记录, 恢复线程B的上文继续执行B的下文)
![23424214](https://img-blog.csdnimg.cn/142fa590ca4344a9ad972711bf6ec524.png

6 ConcurrentHashMap 并发

ConcurrentHashMap 其实底层是初始16个HashMap, 在扩容时乘以2, 保证数组长度是2的幂次方, 减少hash冲突,数组下标计算方式(n-1) & hash, 而2的幂次方-1的二级制数都是1, hash冲突可能性最小.

在多线程中, put会进行hash计算, 然后加锁写入相应的hashMap中, 因为初始有16个hashmap, 则可以同时处理多线程的问题

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

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

相关文章

【Java可执行命令】(十一)Java 密钥库和证书管理工具keytool:玩转密钥库和证书管理,深入解析keytool工具的应用与技巧~

Java可执行命令之keytool 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 生成证书请求&#xff1a;keytool -certreq3.3 导出证书&#xff1a;keytool -exportcert3.4 生成密钥对&#xff1a;keytool -genkeypair3.5 导入证书或证书链&#xff1a;keytool -importcert3…

基于STM32的直流电机调速系统

目录 基于STM32的直流电机调速系统一、原理图二、部分代码三、视频演示 基于STM32的直流电机调速系统 功能&#xff1a; 1.通过LCD屏幕显示实时两个电机的占空比 2.通过按键调整电机1和2的加减速 3.通过L298N驱动两个直流电机完成调速 一、原理图 二、部分代码 #include &qu…

基于Spring Boot + Vue社区管理系统的设计与实现

1、项目介绍 Spring Boot 是一个用于构建 Java 应用程序的开源框架&#xff0c;它使得开发者可以轻松地创建独立的、生产级别的 Spring 应用程序。Vue.js 是一个流行的 JavaScript 框架&#xff0c;用于构建现代化的、响应式的社区管理系统是一个用于管理社区活动、用户信息和…

Vue发布新版本,强制更新代码的方式

public下新建version.json文件定义版本 {"version":"1.1.0" } util下新建updateVersion.js import axios from axios; import { Loading } from element-ui; var t1; var t2; export async function isNewVersion() {var randomNumberMath.random() co…

[软件工具]左键连发工具左键连点工具使用教程

左键连发软件是一个可以点击一下自动左键连续点击指定次数的软件&#xff0c;比如你设置20次&#xff0c;当你点击一次松开鼠标后&#xff0c;会自动左键连续点击20次。具体使用教程为&#xff0c;我们打开软件 我们可以设置连发次数&#xff0c;默认15次&#xff0c;你可以设置…

Zabbix如何对接Prometheus

一、简介 云原生和容器广泛流行打破传统的技术堡垒&#xff0c;现在Prometheus监控得到越来越多企业应用和探索。对于已经存在Zabbix监控系统的用户又想尝试Prometheus而言&#xff0c;在Zabbix4.2版本及5.0 LTS版本正式发布增加了对Prometheus数据源的接入&#xff0c;后续都…

人机环境系统中的一多分有问题探讨

在一般的事物中&#xff0c;一多关系通常指的是一个事物与多个其他事物之间的关系。一多关系可以带来更多的选择和多样性&#xff0c;使事物更加丰富多样。不同的事物之间相互影响和交融&#xff0c;可以产生新的创意和发展机会&#xff1b;不同事物之间的各种关系需要平衡各自…

leetcode 203.移除链表元素

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;移除链表元素 1️⃣ 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*//*思路1&#xff1a;遍历链表&#xff0c;同时用另一个指针记录当…

IDA c++分析辅助插件ida_medigate使用记录

1.下载插件 IDA_medigate 2.将medigate_cpp_plugin.py放到 ida的plugin文件夹中 plugins/ida-referee/referee.py 放置到plugin中 3.将下载的 ida_medigate 放到IDA 内置的python38的Lib\site-packages\目录下 如:D:\IDA_Pro_7.7\python38\Lib\site-packages4.配置插件搜索…

大厂面试官:软件测试员,你的简历,是如何石沉大海的?

引言 俗话说&#xff1a;知己知彼百战百胜&#xff0c;面试如打仗&#xff0c;不是面试官赢&#xff0c;就是求职者胜。站在面试官的维度来跟求职者聊天&#xff0c;让求职者知道面试官的心理。 因为我本身作为一面多年的大厂面试官&#xff0c;相对来说还是有一些面试经验&am…

股价在5年内暴涨了3000%后,Enphase Energy未来还会继续上涨吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 Enphase Energy股票的关键指标 最近很多人都在关注Enphase Energy&#xff08;ENPH&#xff09;的关键指标&#xff0c;包括该公司第二季度的指引和最近的股价调整。 2023年4月25日收盘后&#xff0c;Enphase Energy公布了…

leetcode每日一题——80.删除有序数组中的重复项II(面试经典150题)

一、题目描述与要求 80. 删除有序数组中的重复项 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用…

NodeJS安装教程(详细)

系列文章 MySQL安装教程&#xff08;详细&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/126037520 MySQL卸载教程&#xff08;详细&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129279265 …

Linux 内核源代码情景分析(一)

系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核 Linux 设备驱动程序 Linux设备驱动开发详解 深入理解Linux虚拟内存管理 Linux 内核源代码情景分析&#xff08;一&#xff09; 文章目录 系列文章目录一、存储管理1、外部设备存储空间的地址映射&#xff08;1&#xff…

LinK3D论文详解

摘要 特征提取和匹配是许多计算机视觉任务的基本部分&#xff0c;例如二维或三维物体检测、识别和配准。众所周知&#xff0c;二维特征提取和匹配已经取得了很大的成功。遗憾的是&#xff0c;在3D领域&#xff0c;由于描述能力差和效率低&#xff0c;目前的方法无法支持3D激光雷…

uniapp在微信开放平台创建移动应用时,如何生成应用签名的问题

包名在打包的时候是必填项&#xff0c;就不多赘述了… 微信开放平台获取应用签名&#xff0c; 场景&#xff1a; 首先需要在手机或者模拟器上下载签名生成工具&#xff0c;下载地址&#xff1a;下载签名生成工具 然后手机打开&#xff0c; 在这里输入你的app打包时的包名&…

【雕爷学编程】Arduino动手做(148)---MD-PS002压力传感器模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

[解决方案] 在linux运行python代码报错(Illegal instruction (core dumped))

PVE修改CPU类型 在运行Python代码时遇到"Illegal instruction (core dumped)"错误时&#xff0c;意味着你的代码尝试在当前CPU架构不支持的指令上运行 1. 利用lscpu命令查看宿机和虚拟机CPU架构&#xff0c;确定宿机和虚拟机是否支持avx指令集 可以发现宿机是支持avx…

暑期学JavaScript【第四天】

日期对象 创建 //创建 const date new Date(); // 默认使用现在时间常用方法 时间戳的获取方式 date.getTime()new Date()Date.now() 倒计时案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta htt…

Redis初步认识

1、概述 redis是一款高性能的NOSQL系列的非关系型数据库&#xff1b; 2、什么是NOSQL&#xff1f; NOSQL(NoSQLNotOnlySQL)&#xff0c;意即“不仅仅是SQL”&#xff0c;是一项全新的数据库理念&#xff0c;泛指非关系型的数据库随着互联网web2.0网站的兴起&#xff0c;传统…