分布式锁(仅供自己参考)

分布式锁:满足分布式系统或集群式下多进程可见并且互斥的锁(使用外部的锁,因为如果是集群部署,每台服务器都有一个对应的tomcat,则每个tomcat的jvm就不同,锁对象就不同(加锁的机制,每个jvm有一个锁监听器,里面存放着是否含有所有加锁的对象。有一次线程来,首先判断该线程所具有的所要加锁的对象在锁监视器中是否有,有则让它等待,没有的话则将加锁的对象加进来,再有线程来判断,则和上面的流程一样))(自己理解的流程,可能不对,请指错)

redis实现分布式锁:

一些主要代码:

因为在加锁的时候new的不同的对象,使得每一个name都是不同的。

如果是在高并发的情况下,会有一点的影响(具体的影响是问的chatgpt的)     

        在高并发情况下,如果每个线程都频繁创建和销毁大量对象,这会对Java堆内存产生一定的影响,主要体现在以下几个方面:

  1. 内存使用压力增大:

    • 大量的对象实例占用堆内存空间,可能会导致堆内存使用率急剧上升。
    • 如果不及时回收无用对象,会造成内存溢出(OutOfMemoryError)的风险。
  2. 垃圾回收压力增大:

    • 大量短暂对象的频繁创建和销毁,会增加Java虚拟机的垃圾回收压力。
    • 垃圾回收频率提高,可能会导致应用的性能下降。
  3. 线程间资源竞争:

    • 多个线程同时在堆内存中分配和释放对象,可能会产生线程安全问题。
    • 如果对象的创建和销毁不够规范,可能会导致内存泄漏等问题。
  4. 缓存命中率下降:

    • 大量短暂对象的创建可能会污染CPU缓存,降低缓存命中率。
    • 这会增加内存访问的开销,拖慢整体应用的性能。

可以采用单例模式,也就是在将锁对象作为成员变量,而不是局部变量。(代码参考(参考这里1))

package com.hmdp.utils;

import org.springframework.data.redis.core.RedisTemplate;

import java.time.Duration;

//redis实现的锁机制
public class OrderOneLock implements Lock {
    private RedisTemplate redisTemplate;

    private String name;
    private final String PREFIX_KEY = "lock:";

    public OrderOneLock(RedisTemplate redisTemplate, String name) {
        this.redisTemplate = redisTemplate;
        this.name = name;
    }

    @Override
    public boolean tryLock(long timeOut) {

        long id = Thread.currentThread().getId();

        Boolean b = redisTemplate.opsForValue().setIfAbsent(PREFIX_KEY + name, id, Duration.ofSeconds(timeOut));
        return Boolean.TRUE.equals(b);
    }

    @Override
    public void unLock() {

        redisTemplate.delete(PREFIX_KEY+name);
    }
}





//加锁
 Long userId = UserHolder.getUser().getId();

        //获取锁,看是否获取成功

        OrderOneLock oneLock = new OrderOneLock(redisTemplate,"order:"+userId); //选择userId是因为每个用户只能选择一单,如果没有userid则全部都使用一单
        boolean b = oneLock.tryLock(120);
        if (!b)
        {
            return Result.fail("只能购买一单");
        }

        //能执行到这里,说明加锁成功,则之后必须不管怎么都要释放锁,所以选择finally
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.getResult(voucherId, userId);
        } finally {
            oneLock.unLock();
        }

参考这里(1)

因为此代码较上边的代码,是为了避免在高并发的情况的大量的创建和销毁对象,而造成性能上的影响。但也是这段代码毕竟麻烦,在于是在销毁锁的时候需要传参数,毕竟有舍有得(具体业务具体分析)。

代码:

package com.hmdp.utils;

import org.springframework.data.redis.core.RedisTemplate;

import java.time.Duration;

public class OrderOneLock implements Lock {
    private RedisTemplate redisTemplate;

    //private String name;
    private final String PREFIX_KEY = "lock:";
    private String workName; //具体的业务名字

    public OrderOneLock(RedisTemplate redisTemplate, String name) {
        this.redisTemplate = redisTemplate;
        this.workName = name;
    }

    @Override
    public boolean tryLock(long timeOut,String name) {

        long id = Thread.currentThread().getId();

        Boolean b = redisTemplate.opsForValue().setIfAbsent(PREFIX_KEY + workName+name, id, Duration.ofSeconds(timeOut));
        return Boolean.TRUE.equals(b);
    }

    @Override
    public void unLock(String name) {

        redisTemplate.delete(PREFIX_KEY+workName+name);
    }
}

上述代码还会造成一种情况,请看下图:

        由于线程1的阻塞,导致redis的key的过期。这时候key过期,线程2就可以拿到锁了,如果这时候线程1,业务完成结束,肯定是需要释放锁的,但这个时候释放的是线程2的锁,而不是线程1它本身的锁。线程2的锁释放之后,但是线程2的业务还未完成,线程3也就来了,导致了有两个线程同时运行,于我们加锁(只能由一个线程执行相违背),而且前一个线程会释放后一个线程的锁。

解决方案:

        给每一个线程的锁都弄一个标识,使得每一个线程都只能由它本身释放,或者过期(这里还没有解决会有多个线程同时执行)

        使用uuid和当前线程号给每一个线程一个标识。uuid在同一个jvm可能相同,但是在不同的jvm之间不同(每一个jvm运行在不同的机器或者虚拟机上)。原因:

  1. UUID生成算法: 在Java中,UUID通常使用RFC 4122中定义的算法生成。这个算法利用了时间戳、MAC地址、随机数等多种因素来生成唯一的UUID。

  2. 时间因素: 算法中使用了时间戳作为生成因素之一。不同的JVM,即使在同一时间生成,由于机器时钟的微小差异,也会导致生成的UUID不同。

  3. 空间因素: UUID的算法还会利用MAC地址作为生成因素。不同的JVM运行在不同的服务器/虚拟机上,MAC地址必不同,这也会导致生成的UUID不同。

           在加上同一个jvm上的线程号是不同的,也就构成了唯一一个标识(jvm使得线程号是递增的)

代码:(自己比较与上面代码的差别)

package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.RedisTemplate;

import java.awt.*;
import java.time.Duration;

public class OrderOneLock implements Lock {
    private RedisTemplate redisTemplate;

    private String name;
    private final String PREFIX_KEY = "lock:";
    private final String mark = UUID.randomUUID().toString(true)+"-";

    public OrderOneLock(RedisTemplate redisTemplate, String name) {
        this.redisTemplate = redisTemplate;
        this.name = name;
    }

    @Override
    public boolean tryLock(long timeOut) {

        long id = Thread.currentThread().getId();

        Boolean b = redisTemplate.opsForValue().setIfAbsent(PREFIX_KEY +name, mark+id, Duration.ofSeconds(timeOut));
        return Boolean.TRUE.equals(b);
    }

    @Override
    public void unLock() {
        long id = Thread.currentThread().getId();
        String o = (String) redisTemplate.opsForValue().get(PREFIX_KEY + name);
        String s = mark + id;
        if(s.equals(o))
        {
            redisTemplate.delete(PREFIX_KEY+name);
        }
    }
}

新的情况出现:

如上图:在线程1执行业务完成之后,需要释放锁,在已判断了他是属于他的锁,就要执行 redisTemplate.delete(PREFIX_KEY+name);这句时发生了阻塞(jvm的垃圾回收机制,导致所有代码都不能运行)。一直阻塞。阻塞时间超过了key的过期时间。锁释放。等到阻塞结束,这时又会有线程2进来,线程1也是执行 redisTemplate.delete(PREFIX_KEY+name);这句。这时候线程1就会释放线程2的锁。释放之后就会有线程3进来,这时就会有两个线程执行这个业务(发生了并发),不符合我们的要求。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(自用)共享单车服务器(二) 项目日志

stdin、stdout、stderr 注意&#xff1a;stderr是不缓存的&#xff0c;stdout则进行行间缓存。接下来我们看下行间缓存的效果&#xff0c;请参考以下代码&#xff1a; #include "stdio.h" #include <unistd.h>int main(int argc, char** argv) {for (int i 0…

万字长文MySQL Binlog 详细指南

目录 第一阶段 MySQL Binlog 基础用法1. Binlog基本概念1.1 什么是Binlog1.2 Binlog的作用1.3 Binlog格式 2. 配置和管理Binlog2.1 开启Binlog2.2 设置Binlog文件大小和保留时间2.3 查看Binlog状态 3. Binlog的实际应用3.1 数据恢复3.2 主从复制3.3 审计 4. Binlog工具使用4.1 …

吴恩达机器学习笔记2.1 - 什么是机器学习

吴恩达机器学习笔记2.1 - 什么是机器学习 最早的机器学习 1959年&#xff0c;亚瑟塞缪尔(Arthur Samuel)将机器学习定义为“Field of study that gives computers the ability to learn without being explicitly programmed”&#xff08;无需编程即可学习的研究领域&#xf…

谷粒商城 - 编写一个自定义校验注解

目录 开始 未来实现效果 第一步&#xff1a;编写自定义校验注解 第二步&#xff1a;编写自定义校验器 第三步&#xff1a;编写配置文件 效果演示 开始 未来实现效果 编写一个 ListValue 注解&#xff0c;可以实现功能有&#xff1a; 限定字段的值&#xff0c;例如指定只…

解读BASE理论:高可用性与性能的完美平衡

Base概念 BASE 理论是一种处理大规模分布式系统中的数据一致性问题的思路。相比于传统的严格一致性&#xff0c;它更灵活&#xff0c;适用于那些需要高可用性和性能的系统。BASE 理论由三个部分组成&#xff1a; 基本可用&#xff08;Basically Available&#xff09; 基本可用…

Unity通过NDK实现C#与C++之间的相互调用

由于一些历史遗留问题&#xff0c;我们项目还在使用一套C实现的Box2D定点数的库&#xff0c;由于最近修改了视野算法所以需要重新打包安卓的【.so】文件&#xff0c;特此记录 1、关于NDK 在Android平台&#xff0c;C/C需通过NDK编译成动态链接库.so文件&#xff0c;然后C#中通过…

天士力“数智本草”大模型如何赋能中药药品研发工作?

天士力“数智本草”大模型如何赋能中药药品研发工作&#xff1f; 目前&#xff0c;“数智本草”大模型如何配合天士力研发工作&#xff1f;对新药研发、中药二次开发等产生了什么价值&#xff1f;能否介绍一些具体的成果案例&#xff1f; “数智本草”大模型目前已经形成智能问…

免费下载工具 -- Free Download Manager(FDM) v6.24.0.5818

软件简介 Free Download Manager (FDM) 是一款免费的功能强大的下载管理软件&#xff0c;适用于多种操作系统&#xff0c;包括 Windows、macOS、Android 和 Linux。这款软件的特色在于它快速、安全且高效的下载能力。它可以下载各种热门网站的影片&#xff0c;支持 HTTP/HTTP…

【内网渗透】MSF渗透阶段的常用指令笔记

目录 渗透阶段划分 msfvenom 常用参数 各平台生成payload命令 Meterpreter Meterpreter的常用命令 基本命令 常用命令 针对安卓手机的一些命令 针对Windows的一些命令 文件系统命令 生成木马反弹shell(以linux靶机为例) 木马生成 配置监控 攻击利用 渗透阶段划分…

人工智能算法工程师(中级)课程2-Opencv视觉处理之高级操作

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程2-Opencv视觉处理之高级操作。在上一节课中的OpenCV基础操作我们了解到OpenCV是一个开源的计算机视觉软件库。它提供了各种视觉处理函数&#xff0c;并支持多种编程语言&#xff0c;如…

2-29 基于matlab的CEEMD

基于matlab的CEEMD&#xff08;Complementary Ensemble Empirical Mode Decomposition&#xff0c;互补集合经验模态分解&#xff09;&#xff0c;先将数据精心ceemd分解&#xff0c;得到imf分量&#xff0c;然后通过相关系数帅选分量&#xff0c;在求出他们的样本熵的特征。用…

HTML语言常见标签

语法 HEAD部分的HTML标签 1 标题标签 <title>标题内容</title> 2 段落标签 <meta charset"utf-8"/> BODY部分的HTML标签 1标题标签&#xff08;独占一行&#xff09;<h1>标题内容</h1> 2段落标签&#xff08;独占一行&#xff09;…

FUSE(用户空间文件系统)命令参数

GPT-4 (OpenAI) FUSE (Filesystem in Userspace)是一个允许创建用户空间文件系统的接口。它提供了一个API&#xff0c;让开发者在未修改内核代码的情况下&#xff0c;通过自己的程序实现文件系统。FUSE 文件系统通常通过 mount 命令来挂载&#xff0c;而且这个命令可以接受各…

深度学习--系统配置流程

Win10系统配置双系统Ubuntu18.04 深度学习台式服务器自装练手1.win10磁盘管理2.下载系统镜像制作U盘3.系统安装4. 安装后的系统设置工作5.配置CUDA环境CUDNN安装 深度学习台式服务器自装练手 写在最前 CUDA最高支持11.4 显卡3060 1.win10磁盘管理 首先对原有磁盘进行分区整理…

240709_昇思学习打卡-Day21-文本解码原理--以MindNLP为例

240709_昇思学习打卡-Day21-文本解码原理–以MindNLP为例 今天做根据前文预测下一个单词&#xff0c;仅作简单记录及注释。 一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积 &#x1d44a;_0:初始上下文单词序列&#x1d447;: 时间步当生成EOS标签时&a…

【QML之·基础语法概述】

系列文章目录 文章目录 前言一、QML基础语法二、属性三、脚本四、核心元素类型4.1 元素可以分为视觉元素和非视觉元素。4.2 Item4.2.1 几何属性(Geometry&#xff09;:4.2.2 布局处理:4.2.3 键处理&#xff1a;4.2.4 变换4.2.5 视觉4.2.6 状态定义 4.3 Rectangle4.3.1 颜色 4.4…

系统化学习 H264视频编码(01)基础概念

说明&#xff1a;我们参考黄金圈学习法&#xff08;什么是黄金圈法则?->模型 黄金圈法则&#xff0c;本文使用&#xff1a;why-what&#xff09;来学习音H264视频编码。本系列文章侧重于理解视频编码的知识体系和实践方法&#xff0c;理论方面会更多地讲清楚 音视频中概念的…

基于java+springboot+vue实现的校园二手书交易平台(文末源码+Lw)287

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

【hive】数据采样

参考https://hadoopsters.com/how-random-sampling-in-hive-works-and-how-to-use-it-7cdb975aa8e2&#xff0c;可以直接查看原文&#xff0c;下面只是对原文进行概括和实际性能测试。 1.distribute by sort by2.测试3.map端数据过滤优化采样 在说数据采样之前&#xff0c;需要…

03_Shell变量

【Shell】03_Shell变量 一、环境变量 Linux系统配置文件&#xff08;全局配置文件和用户个人配置文件&#xff09;中定义的变量&#xff0c;提供给所有Shell程序使用 1.1、全局环境变量 1.1.1、配置文件位置 /etc/environment /etc/bashrc&#xff08;或者/etc/bash.bashrc…