走进redisson

        这里作者将大家走进redisson,读完这篇相信加深你对redisson的获取锁,重入,超时,看门狗,发布订阅等原理和功能的理解。

        本文将深入原理代码,给出每行代码的意义以及最后的效果,过程有些枯燥,但探索的过程是快乐的,同时也希望大家看的过程当中去一起去查看源码。

redisson

lock

参数介绍:

  1. long leaseTime:

    • 这个参数指定锁的持有时长。在这段时间结束后,锁会自动释放,除非它被续期或手动解锁。这是一种安全措施,确保在出现问题(如应用崩溃或网络问题)时锁最终会被释放,避免死锁情况的发生。

    • 指定的时间长度通常取决于预期的操作时间和网络延迟。设置适当的租约时间可以防止资源长时间被锁定,并允许其他线程或进程在合理的时间内访问资源。

  2. TimeUnit unit:

    • 这个参数定义了leaseTime的时间单位。TimeUnit是一个枚举类型,提供了时间单位的常量,如TimeUnit.SECONDS, TimeUnit.MILLISECONDS等,使你能以不同的时间单位指定锁的租约时间。

    • 使用TimeUnit参数可以提供额外的灵活性和清晰度,允许调用者根据需要轻松地指定租约时间,而不必担心时间单位的转换。

  3. boolean interruptibly:

    • 这个布尔值指定了锁获取的行为应该如何响应中断。如果设置为true,那么当线程在等待获取锁的过程中被中断时,它会响应这个中断,并且可能会抛出一个InterruptedException。这允许线程在等待获取锁时可以被取消或中断。

    • 如果设置为false,即使线程被中断,也会继续等待锁,不响应中断。这通常用于那些必须等到获取锁后才能继续的情况,确保操作的完成。

    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        //获取当前线程的id
        long threadId = Thread.currentThread().getId();
        //ttl(Time To Live)代表锁的剩余存活时间。它是尝试获取锁时返回的值,表示锁在自动释放之前还能保持多久
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        //如果为为null,代表锁获取成功。
        if (ttl == null) {
            return;
        }
		//redis的发布订阅机制,用来订阅通知。
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        //负责发送命令和处理响应
        commandExecutor.syncSubscription(future);

        try {
            //这段代码实现了一个循环,不断尝试获取锁,直到成功。在尝试获取锁的过程中,根据不同的情况采取不同的行动,比如等待一定时间后重试,或者在被中断时根据interruptibly变量的值决定是否继续等待
            while (true) {
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired,锁获取成功,直接退出循环
                if (ttl == null) {
                    break;
                }

                // waiting for message
                if (ttl >= 0) {
                    try {
 //这里尝试等到锁知道锁可用或者超时。
                        getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        //如果等待过程当中被中断了,参数interruptibly为true,抛出异常,否则继续等待。
                        if (interruptibly) {
                            throw e;
                        }
                       //释放锁的人释放之后会发出一个信号量,
                        getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                    //小于0,代表没有超过指定时间
                } else {
                    if (interruptibly) {
 //选择阻塞调用acquire()(响应中断)或acquireUninterruptibly()(不响应中断)。                      
                        getEntry(threadId).getLatch().acquire();
                    } else {
   //getLatch()提供的信号量成为了一个同步点,线程可以在这里等待,直到它们可以安全地继续执行
                        getEntry(threadId).getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }

tryAcquire

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
    //返回一个获取锁的方法
    return get(tryAcquireAsync(leaseTime, unit, threadId));
}

 tryAcquireAsync

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        //如果leaseTime不等于-1,意味着用户指定了一个有效的租约时间,就不会走自带的逻辑。
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        //如果没有设置leaseTime的值,方法获取默认的锁看门狗超时时间,再次调用tryLockInnerAsync。这意味着将使用默认的超时设置来尝试获取锁。
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            //e表示回调的异常,如果为null则说明操作完成,否则直接返回。
            if (e != null) {
                return;
            }

            // lock acquired
            //ttlRemaining是回调的结果,表示锁的剩余存活时间。
            if (ttlRemaining == null) {
                //在锁已经成功获取的情况下,看当前线程是否需要续订锁。
                scheduleExpirationRenewal(threadId);
            }
        });
        //如果锁当前被其他线程持有,这个值可能表示要等待多长时间才有可能再次尝试获取锁。
        //null:通常表示锁已成功被当前线程获取,没有剩余等待时间。
        return ttlRemainingFuture;
    }

        订阅发布机制目的是为了在当前线程不能立即获得锁时,订阅一个通知,当锁被释放或变为可用时,当前线程可以得到通知。通过这种方式,Redisson 实现了一个高效且响应迅速的分布式锁机制,允许多个线程或进程协调地访问共享资源。

ExprirationEntry

用于管理锁续约机制中线程和超时信息的数据结构

public static class ExpirationEntry {
        //一个映射,键是线程ID(Long),值是计数器(Integer),用于跟踪每个线程对应的锁请求次数。LinkedHashMap保持了插入顺序,这可能对确定哪个线程首先请求锁有用。
        private final Map<Long, Integer> threadIds = new LinkedHashMap<>();
    //一个Timeout类型的变量,用来存储与该锁条目关联的超时任务。标记为volatile以确保在多线程环境中线程安全地访问。
        private volatile Timeout timeout;
        
        public ExpirationEntry() {
            super();
        }
        //添加或增加一个线程ID的计数。如果该线程ID已存在,则增加其计数;如果不存在,则将其计数设置为1。
        public void addThreadId(long threadId) {
            Integer counter = threadIds.get(threadId);
            if (counter == null) {
                counter = 1;
            } else {
                counter++;
            }
            threadIds.put(threadId, counter);
        }
    //检查threadIds映射是否为空,即没有任何线程ID与此锁条目关联。
        public boolean hasNoThreads() {
            return threadIds.isEmpty();
        }
        public Long getFirstThreadId() {
            if (threadIds.isEmpty()) {
                return null;
            }
            return threadIds.keySet().iterator().next();
        }
        public void removeThreadId(long threadId) {
            Integer counter = threadIds.get(threadId);
            if (counter == null) {
                return;
            }
            counter--;
            if (counter == 0) {
                threadIds.remove(threadId);
            } else {
                threadIds.put(threadId, counter);
            }
        }
        
        
        public void setTimeout(Timeout timeout) {
            this.timeout = timeout;
        }
        public Timeout getTimeout() {
            return timeout;
        }
        
    }

syncSubscription

同步等待一个异步订阅操作的完成,并处理超时以及中断情况,确保资源正确管理和异常处理逻辑得到妥善执行

public void syncSubscription(RFuture<?> future) {
    //获取redis的配置信息,该配置信息存储在MasterSlaveServersConfig对象中,MasterSlaveServersConfig可能包含了与主从服务器相关的配置设置
    MasterSlaveServersConfig config = connectionManager.getConfig();
    try {
        //计算超时时间,基于配置中的单次操作超时时间(getTimeout())、重试间隔(getRetryInterval())以及重试次数(getRetryAttempts())计算得出
        int timeout = config.getTimeout() + config.getRetryInterval() * config.getRetryAttempts();
        //等待future的完成
        if (!future.await(timeout)) {
            ((RPromise<?>) future).tryFailure(new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."));
        }
       // 如果在指定的超时时间内future没有完成(即await返回false),则尝试将future标记为失败,并抛出一个带有超时信息的
    } catch (InterruptedException e) {
        //处理线程中断
        Thread.currentThread().interrupt();
    }
    //同步完成,忽略中断,最后,调用syncUninterruptibly()确保当前线程在future完成之前不会继续执行,syncUninterruptibly()会忽略线程的中断状态,确保等待直到操作完全完成
    future.syncUninterruptibly();
}

unsubscribe

在完成所有操作后取消订阅,避免内存泄露或不必要的资源占用

 public void unsubscribe(E entry, String entryName, String channelName) {
        //这个信号量用于控制对特定操作的并发访问,确保在对订阅进行更改时的线程安全。
        AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
        semaphore.acquire(new Runnable() {
            @Override
            public void run() {
                //如果entry.release() == 0,则表示相关资源可以被释放
                if (entry.release() == 0) {
                    // just an assertion
                    boolean removed = entries.remove(entryName) == entry;
                    if (!removed) {
                        throw new IllegalStateException();
                    }
                    //如果成功释放资源,调用service的unsubscribe方法取消订阅特定的频道,同时传入信号量以维护操作的同步性。
                    service.unsubscribe(new ChannelName(channelName), semaphore);
                } else {
                    //如果entry没有被完全释放(即release()方法返回非零值),则调用semaphore.release()来释放信号量,允许其他操作继续进行
                    semaphore.release();
                }
            }
        });

    }

tryLockInnerAsync

T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
//key=Keys[1] --锁的key
        //threadId--线程唯一id
        //releaseTime-锁自动释放时间
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                                              //首先判断锁是否存在
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                                              //不存在,获取锁
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                              //调用expire设置有效期
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                              //返回结果
                      "return nil; " +
                  "end; " +
                                              //锁已经存在了,判断threadId是不是自己的线程
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                              //是自己的,重入次数+1
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                                              //设置有效期
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                                           //如果不是自己的就返回失败。
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }
    

scheduleExpirationRenewal

        确保锁的过期时间被适时续约,特别是在锁被长时间持有时。它通过维护一个包含所有需要续约的锁条目的映射(EXPIRATION_RENEWAL_MAP)来实现。每个条目代表一个锁,包含一个或多个希望续约该锁的线程ID。如果条目已存在,说明已经有其他线程计划续约这个锁,当前线程ID将被添加到这个条目中;如果条目不存在,将创建一个新的条目,并触发续约操作。这种机制允许多个线程协调地续约同一个锁,确保锁在需要时保持活动状态,避免因超时而导致的锁自动释放。

private void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
       RedissonLock的所有实例都可以看到EXPIRATION_RENEWAL_MAP,因为他是静态的。一个RedissonLock类会创建出很多实例,每个锁都有自己的名字,有自己唯一entry。
        ExpirationEntry oldEntry = 
 EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
       //oldEntry不为null,说明映射中已经有一个条目与当前锁相关联,那么就将当前线程ID添加到这个旧条目中,表明当前线程也希望续约这个锁的过期时间。
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            //如果没有旧条目(即oldEntry为null),表示这是第一次尝试续约当前锁的过期时间,那么就将当前线程ID添加到新条目中,并调用renewExpiration方法来实际执行续约操作。
            entry.addThreadId(threadId);
            renewExpiration();
        }
    }

 renewExpiration

private void renewExpiration() {
     //获取与当前锁相关联的ExpirationEntry对象。这个对象可能包含了关于锁续约所需的信息,如相关的线程ID
    
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
     //没有找到过期条目,方法直接返回,锁可能被释放或者没有续约必要
        if (ee == null) {
            return;
        }
        //创建一个新的Timeout任务,该任务将在指定的延迟后执行
        Timeout task = 
 //调度异步任务的方法,用于在指定的时间后执行代码。这里使用了internalLockLeaseTime / 3作为延时,表示在锁租约时间的三分之一后执行。
            commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                //再次从映射中获取锁的过期条目。
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                //获取第一个等待续约的线程ID。
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                //如果线程ID存在,调用renewExpirationAsync(threadId)异步续约锁。
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        //如果出现异常,记录错误日志。
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    //如果续约成功(res为真),则递归调用renewExpiration以再次调度续约,确保锁在持有期间保持活动。
                    if (res) {
                        // reschedule itself
                        //重置有效期
                        renewExpiration();
                    }
                });
            }
            //意味着在锁的租约时间过去三分之一之前,尝试对其进行续约。
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        //更新过期条目的定时器
        ee.setTimeout(task);
    }

只有在释放锁的时候才会停止续期。

unlockAsync

 void cancelExpirationRenewal(Long threadId) {
        //根据锁的名字来取
        ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (task == null) {
            return;
        }
        
        if (threadId != null) {
            task.removeThreadId(threadId);
        }
        
        if (threadId == null || task.hasNoThreads()) {
            task.getTimeout().cancel();
            EXPIRATION_RENEWAL_MAP.remove(getEntryName());
        }
    }

tryLock

 public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
      //获取成功
        if (ttl == null) {
            return true;
        }
        //等待的时间为减掉获取锁的时间
        time -= System.currentTimeMillis() - current;
      //等待时间小于0就不等了
        if (time <= 0) {
            acquireFailed(threadId);
            return false;
        }
        //严格控制时间,但是刚获取一次,立刻获取成功率不大,这时候我们可以用到了之前的发布订阅,获取到该县城订阅的别的线程是否释放锁的信息
        current = System.currentTimeMillis();
      //订阅与锁相关的信息
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
      //等待订阅完成,或者给定的等待时间time耗尽,
        if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
            //订阅未在指定时间完成
            if (!subscribeFuture.cancel(false)) {
                //尝试取消订阅操作。如果取消失败(即订阅操作已完成或无法取消),则设置一个完成时的回调来处理结果。
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            //调用acquireFailed方法处理获取锁失败的逻辑
            acquireFailed(threadId);
            //返回false,表示未能成功获取锁。
            return false;
        }

        try {
            //精确控制之前耗尽的时间
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(threadId);
                return false;
            }
        //不断地尝试
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
 //如果ttl小于剩余时间且大于0,通过getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);等待直到锁可能变为可用或直至ttl时间结束。                  
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
 //等待剩余地总时间                  
                    getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }
            }
        } finally {
            //无论结果如何,都取消对锁释放消息的订阅,以避免内存泄漏或其他潜在问题
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

unlockInnerAsync

解锁逻辑

 protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                                              //判断锁是不是自己持有
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                                              //如果不是自己的就返回
                    "return nil;" +
                "end; " +
                                              //如果是自己的锁,冲入次数-1
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                                              //判断是不是0,大于0说明不能释放,等于0就可以释放
                "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                "else " +
                                              //直接删除了
                    "redis.call('del', KEYS[1]); " +
                                              //将信息发送到指定的频道,任何订阅了该频道的客户端都可以接收到这个信息,在这个上下文中,它用于在锁被释放时通知其他可能正在等待这个锁的客户端
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }
    

图示流程

黑马的图,不重复造轮子

不过大家要记住如果设置了超时时间看门狗线程就不起作用了。也就是leasetime

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

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

相关文章

GitHub Copilot怎么取消付费?

0. 前言 GitHub Copilot非常好用&#xff0c;还没有使用过的同学可以参考教程白嫖一个月&#xff1a;【保姆级】VsCode 安装GitHub Copilot实操教程 GitHub Copilot每月10美元的费用对于一些用户来说可能是一笔不小的开销。如果你已经完成了GitHub Copilot的免费试用&#xf…

在conda下的sklearn库安装

比较坑的是它这个库在conda下不叫这个名字&#xff0c;所以直接conda install sklearn是不行的&#xff0c;输入下面的指令就能直接安装啦。 conda install scikit-learn

【数据结构】猛猛干7道链表OJ

前言知识点 链表的调试技巧 int main() {struct ListNode* n1(struct ListNode*)malloc(sizeof(struct ListNode));assert(n1);struct ListNode* n2(struct ListNode*)malloc(sizeof(struct ListNode));assert(n2);struct ListNode* n3(struct ListNode*)malloc(sizeof(struc…

docker方式进行pytorch多机多卡分布式训练

docker ip共享与gpu指定 1)ip共享 docker网络有多种,这里选择host直接用宿主机的ip 2)指定gpu docker方式进行pytorch多机多卡分布式训练 nvidia docker 容器介绍链接:https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes/ docker用的nvidia官方镜像…

练习11 Web [极客大挑战 2019]Http

页面没有上传文件的地方&#xff0c;尝试查看前端源码中有没有任何内容&#xff0c;以及Fuzz手段查找php文件进行重放改包 本题要点&#xff1a; 伪造请求域名&#xff08;Referer&#xff09;&#xff0c;伪造浏览器标识&#xff08;User-Agent&#xff09;&#xff0c;伪造IP…

QT(6.5) cmake构建C++编程,调用python

一、注意事项 explicit c中&#xff0c;一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)&#xff0c;承担了两个角色&#xff0c;构造器、类型转换操作符&#xff0c; c提供关键字explicit&#xff0c;阻止转换构造函数进行的隐式转换的发生&#…

vue3 + ts +element-plus + vue-router + scss + axios搭建项目

本地环境&#xff1a; node版本&#xff1a;20.10.0 目录 一、搭建环境 二、创建项目 三、修改页面 四、封装路由vue-router 五、element-plus 六、安装scss 七、封装axios 一、搭建环境 1、安装vue脚手架 npm i -g vue/cli 2、查看脚手架版本 vue -V3、切换路径到需…

DC-1靶场

一.环境搭建 下载地址 http://www.five86.com/downloads/DC-1.zip 把桥接设置为nat模式&#xff0c;打开靶机的时候会提示几个错误&#xff0c;点击重试即可 启动靶机&#xff0c;如下图所示即可 二.开始打靶 1.信息收集 arp-scan -l 扫描跟kali&#xff08;攻击机&…

李宏毅深度强化学习导论——基本概念

引言 这是李宏毅老师深度强化学习视频的学习笔记&#xff0c;主要介绍强化学习的基本概念&#xff0c;从直观的角度去教大家如何理解强化学习的知识而不是理论的角度&#xff0c;所以包含的数学公式不多。 什么是强化学习 我们知道监督学习需要人类给机器一个标签&#xff0c…

Docker专题-04 Nginx部署

Docker专题-04 Nginx部署 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com 转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-03-21 csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注…

3.windows下Ubuntu,sratoolkit软件,从ncbi的sra数据库下载数据。

NCBI的SRA&#xff08;Sequence Read Archive&#xff09;数据库是一个公共数据库&#xff0c;用于存储高通量测序数据&#xff0c;包括基因组、转录组和表观基因组等数据。研究人员可以在SRA数据库中找到各种生物样本的测序数据&#xff0c;并进行检索、下载和分析。 在NCBI的…

启扬RK3568核心板,助力外科手术机器人迈向智能诊疗之路

外科手术机器人是将机器人技术与外科手术相结合的高端医疗装备&#xff0c;可以帮助医生制定手术计划&#xff0c;提高手术过程中的准确度&#xff0c;并将传统技术转化为数字程序。通过利用机械控制、视觉成像、光电技术、人工智能等技术&#xff0c;外科手术机器人在医疗领域…

全国产飞腾+FPGA架构,支持B码+12网口+多串电力通讯管理机解决方案

行业痛点: 中国的电力网络已经成为当今世界覆盖范围最广、结构最为复杂的人造科技系统。随着国家和各部委颁布了一系列法律法规&#xff0c;如国家颁布的《中华人民共和国网络安全法》、工信部颁布的《工业控制系统信息安全防护指南》、发改委颁布的14号令《电力监控系统安全防…

动态规划课堂7-----两个数组的dp问题(等价代换)

目录 引言&#xff1a; 例题1&#xff1a;最长公共子序列 例题2&#xff1a;不同的子序列 例题3&#xff1a;通配符匹配 例题4&#xff1a;正则表达式 结语&#xff1a; 引言&#xff1a; 本节我们就要进入两个数组的dp问题的学习&#xff0c;通过前面几个章节的学习&…

深入剖析JavaScript引擎的工作原理

文章目录 导文什么是JavaScript引擎的工作原理&#xff1f;1. 解析阶段解析器&#xff08;Parser&#xff09; 2. 编译阶段3. 执行阶段解释器&#xff08;Interpreter&#xff09;优化器&#xff08;Optimizer&#xff09; 4. 垃圾回收阶段垃圾回收器 其他 导文 JavaScript引擎…

如何用SCSS制作小铃铛振动/震动/摇晃/晃动的特效/效果?

放大了看效果 ​​​​​​​​​​​​​​ // 摇晃小铃铛振动/震动/摇晃/晃动的特效/效果---------------------------------------- [sg-shaking] {display: inline-block;transform-origin: center top;animation: sg-shaking 1s alternate forwards; }keyframes sg-shaki…

【Apache ShenYu源码】如何实现负载均衡模块设计

ShenYu是一个异步的&#xff0c;高性能的&#xff0c;跨语言的&#xff0c;响应式的 API 网关。有关ShenYu的介绍可以戳这。 一、前瞻 今天我们尝试不同的代码阅读方式&#xff0c;按模块来去阅读源码&#xff0c;看看效果如何。 本次阅读锁定在shenyu-loadbalancer&#xf…

Java安全 反序列化(3) CC1链-TransformedMap版

Java安全 反序列化(3) CC1链-TransformedMap版 本文尝试从CC1的挖掘思路出发&#xff0c;理解CC1的实现原理 文章目录 Java安全 反序列化(3) CC1链-TransformedMap版配置jdk版本和源代码配置前记 为什么可以利用一.CC链中的命令执行我们可以尝试一下通过InvokerTransformer.tr…

Windows环境下编译ffmpeg 6.1源码--Virtual Studio + Msys2方式

环境准备 约定&#xff1a;源码全部放到sources下&#xff0c;目录结构说明 /d/java/ffmpeg #工程工目录 ├── build #存放编译文件的目录&#xff0c;子目录为具体模块的构建目录 │ ├── fdk-aac │ ├── ffmpeg │ └── x264 ├── instal…

O2OA(翱途)开发平台前端安全配置建议(一)

O2OA开发平台是一个集成了多种功能的开发环境&#xff0c;前端安全在其中显得尤为重要。前端是用户与平台交互的直接界面&#xff0c;任何安全漏洞都可能被恶意用户利用&#xff0c;导致用户数据泄露、非法操作或系统被攻击。因此&#xff0c;前端安全是确保整个系统安全的第一…