当前位置: 首页 > news >正文

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官

  • ReentrantLock是什么,有什么作用
  • ReentrantLock的使用
  • ReentrantLock源码解析
    • ReentrantLock#lock方法
      • FairSync#tryAcquire方法
      • NonfairSync#tryAcquire方法
    • ReentrantLock#unlock方法
  • ReentrantLock的其他方法简介
    • ReentrantLock#lockInterruptibly
    • ReentrantLock#tryLock
    • ReentrantLock#tryLock(带参数)
    • ReentrantLock#newCondition

往期文章:

  • 人人都能看懂的图解java.util.concurrent并发包源码系列 ThreadPoolExecutor线程池
  • 图解java.util.concurrent并发包源码系列,原子类、CAS、AtomicLong、AtomicStampedReference一套带走
  • 图解java.util.concurrent并发包源码系列——LongAdder
  • 图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官

上一篇文章(图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官 )介绍了AQS的原理和源码,然后使用AQS实现了一个排他锁,这次我们看看Java的并发包里面基于AQS实现的锁工具类——ReentrantLock。本文会介绍ReentrantLock的功能,以及ReentrantLock如何通过AQS实现它的功能,但是不会再介绍AQS的相关原理,关于AQS的原理和源码,可以看上一篇文章。

ReentrantLock是什么,有什么作用

ReentrantLock是可重入锁,当我们有一些资源需要互斥访问,或者有某一块代码需要互斥执行时,可以使用ReentrantLock加锁,对互斥资源和互斥代码块起到一个保护作用,保证同一时刻只会有一个线程访问。

在这里插入图片描述

可重入的意思就是当一个线程获取到锁之后,如果它再次获取同一把锁,是可以获取成功的。而不可重入锁则是当前线程获取到锁以后,如果它再次获取,是获取不成功的,也就是自己把自己给阻塞住了,我们上一篇文章最后面的例子就是一个不可重入锁。

在这里插入图片描述

ReentrantLock的使用

ReentrantLock的用法非常简单,它提供了lock方法用于获取锁,unlock方法用于释放锁。

调用lock方法后,当前线程会去获取锁,获取不到锁会阻塞等待,直到获取到锁,该方法才会返回。如果重复调用多次lock方法,就重复加锁多次,当前线程不会阻塞(可重入),但是它需要多次释放锁。

调用unlock方法,当前线程就会释放锁,然后唤醒其他阻塞等待获取锁的线程。

我们还是用上一篇文章最后的那个例子,去观察ReentrantLock的作用。

    public static void main(String[] args) throws InterruptedException {int[] num = {0};// 创建一个ReentrantLock对象ReentrantLock reentrantLock = new ReentrantLock();Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {Thread thread = new Thread(() -> {for (int j = 0; j < 10000; j++) {try {// 加锁reentrantLock.lock();num[0]++;} finally {// 释放锁,防止finally块中是因为防止抛异常导致锁得不到释放reentrantLock.unlock();}}});thread.start();threads[i] = thread;}for (int i = 0; i < threads.length; i++) {threads[i].join();}System.out.println(num[0]);}

在这里插入图片描述

可以看到输出结果是正确的。

用法跟我们上一篇文章最后面的例子几乎是一模一样的。首先要创建一个ReentrantLock对象,然在线程try代码块中调用ReentrantLock的lock方法进行加锁,在finally代码块中调用ReentrantLock的unlock方法进行解锁操作,解锁操作在finally块中是因为防止线程抛异常导致锁得不到释放。

ReentrantLock源码解析

接下来看看ReentrantLock的lock方法和unlock方法的内部源码,看看它是怎么实现锁的获取和释放的。

ReentrantLock#lock方法

ReentrantLock#lock

    public void lock() {sync.lock();}

可以看到和我们上一篇文章的例子一样,都是有一个内部类,这个内部类肯定继承了AQS。但是它这里没有直接调用AQS的acquire方法,而是调用了一个lock方法,我们进去lock方法看一看。

abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;abstract void lock();// ...省略了下面的代码
}

可以看到Sync继承了AQS,但是Sync本身还是一个抽象类,而lock方法也是一个抽象方法。

在这里插入图片描述

可以看到Sync有两个实现类,FairSync和NonfairSync。

在这里插入图片描述

也就是说ReentrantLock内部的sync有可能是FairSync,也有可能是NonfairSync。那什么时候是FairSync、什么时候是NonFairSync呢?我们看看ReentrantLock的构造方法就知道了。

    /*** 默认非公平锁*/public ReentrantLock() {sync = new NonfairSync();}/*** fair为true时,是公平锁,为false则是非公平锁*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

ReentrantLock可以实现公平锁和非公平锁的功能,默认是非公平锁,如果我们调用有参构造方法,传递的fair参数是true就是公平锁,使用的sync就是FairSync,否则就是非公平锁,使用的就是NonfairSync。

在这里插入图片描述

公平锁就是当一个线程获取锁时,会先看一下队列中是否有线程等待获取锁,如果有,那么它会入队列。非公平锁则相反,如果一个线程要获取锁,那么不管三七二十一,先尝试获取一下,如果获取成功,则不用进队列。非公平锁的效率是比公平锁高的,所有默认的就是非公平锁。

我看一下lock方法的具体实现。

    static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}// ......省略下面的代码}

FairSync的lock方法直接调用了AQS的acquire方法,与我们上一篇文章的例子一致。

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// ......省略下面的代码}

NonfairSync的lock方法,会使用AQS的compareAndSetState方法尝试获取锁,如果CAS成功那么就不需要走acquire方法的逻辑了,如果CAS失败,才会调用AQS的acquire方法。

在这里插入图片描述

FairSync#tryAcquire方法

FairSync的lock方法会调用AQS的acquire方法,acquire方法会调用tryAcquire方法,然后会进入到FairSync的tryAcquire方法。

        protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取AQS中的state变量if (c == 0) { // state等于0表示锁没有被获取if (!hasQueuedPredecessors() && // 查看队列中是否有等待获取锁的线程compareAndSetState(0, acquires)) { // CAS尝试获取锁setExclusiveOwnerThread(current); // 获取锁成功,设置当前线程为占有锁的线程return true; // 返回true,表示告诉AQS获取锁成功}}else if (current == getExclusiveOwnerThread()) { // 锁已被获取,但是获取锁的线程是当前线程int nextc = c + acquires; // state加acquiresif (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc); // 设置state的值return true; // 返回true,表示告诉AQS获取锁成功}return false; // 返回true,表示告诉AQS获取锁不成功}}

FairSync的tryAcquire方法首先会调用AQS的getState方法获取state变量,这里是当state为0才表示锁没有被获取,与我们上一篇文章的例子不一样。

如果state为0,那么锁没有被获取,此时会调用hasQueuedPredecessors()方法检查队列中是否有等待获取锁的线程,如果有,那么当前线程不会获取锁,而是返回false直接进队列排队,如果队列中没有等待获取锁的线程,那么会调用AQS的compareAndSetState方法尝试修改state变量,修改成功表示获取锁成功,此时就会设置当前线程为占有锁的线程,然后方法返回true,表示获取锁成功。

如果state不为0,那么会检查当前线程是否是占有锁的线程,如果是,那么会执行重入的逻辑,然后方法返回true。如果当前线程不是占有锁的线程,那么方法返回false,表示获取锁失败。

在这里插入图片描述

下面看一下hasQueuedPredecessors方法是怎么判断队列中是否有线程的。

    public final boolean hasQueuedPredecessors() {Node t = tail; // 尾节点Node h = head; // 头节点Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}

h != t 判断头节点和尾节点是否不相等,不相等才有可能有线程在队列中,相等的话表示队列为空,那么肯定是没有线程在队列中的。

如果头节点和尾节点不相等了,那么看h.next头节点的后继节点是否为空,如果为空,那么就表示当前队列已经有等待获取锁的线程。那么为什么头尾节点不相等,并且头节点的next指针为空,就表示有线程已经入队列了呢?还有,什么情况下头节点指针和尾节点指针不相等,但是头节点的next指针为null呢?答案就在AQS初始化队列以及线程节点入队的方法中,也就是AQS的enq方法。

    private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node())) // A:使用CAS的方式设置头节点指针指向一个空节点tail = head; // B:设置尾指针指向和头指针指向的同一节点} else {node.prev = t; // C:设置node的prev指针指向当前队列的尾节点if (compareAndSetTail(t, node)) { // D:使用CAS的方式设置尾节点指针指向当前nodet.next = node; // E:设置原来的尾节点的next指针指向当前nodereturn t;}}}}

可以看到AQS的enq方法,分成了5个步骤。注意!这里的前提是AQS的队列还未初始化!

假设现在AQS队列还未初始化,在当前线程获取锁之前,有一个线程执行到了enq方法,然后这个线程执行完A、B、C、D四步,也就是它成功初始化了AQS的队列,然后又用CAS的方式修改尾节点的指针指向它自己的node节点,但是就是没有执行代码E,此时是不是头节点指针和尾节点指针不相等(h != t),并且头节点的next指针是null((s = h.next) == null)?此时这个线程已经算是入了队列的,如果此时有其他线程来获取锁(公平锁),那么是应该要排队的。所以当前线程获取锁的时候,执行到hasQueuedPredecessors()方法,就会返回true,表示已经有线程在队列中等待获取锁了。

那么假设现在AQS队列还未初始化,如果在当前线程获取锁之前,有一个线程执行到了enq方法,然这个线程执行完A、B,没有执行后面三步呢?那么此时头节点指针和尾节点指针还是相等的(h != t 不成立),此时这个线程也不算入队列,所以此时如果有别的线程来调用hasQueuedPredecessors()方法,hasQueuedPredecessors()方法放回false(队列中没有等待获取锁的线程),也是正确的。

如果头节点指针和尾节点指针不相等(h != t 为true),然后头节点的next指针不为null((s = h.next) == null 为false),但是头节点的next指针指向的节点对应的线程等于当前线程(s.thread != Thread.currentThread() 为false),那么hasQueuedPredecessors()方法返回false,当前线程可以尝试获取锁,也是正确的,因为很明显这种情况就是我已经在排队了,并且此时轮到我了。如果头节点的next指针指向的节点对应的线程不是当前线程(s.thread != Thread.currentThread() 为true),hasQueuedPredecessors()方法返回true,那么当前线程就只能去排队。

在这里插入图片描述

FairSync的tryAcquire方法就介绍到这里。

在这里插入图片描述

NonfairSync#tryAcquire方法

NonfairSync的tryAcquire方法里面直接调用了nonfairTryAcquire方法。

        protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}

nonfairTryAcquire方法位于Sync抽象类内部。

        final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 锁没有被获取,尝试获取锁if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 锁已被获取,查看是否是当前线程获取的,如果是,那么执行重入逻辑else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}// 获取锁失败return false;}

进入到Sync的nonfairTryAcquire方法内部,如果判断锁没有被获取(state == 0)就直接先CAS一下,如果CAS成功,该方法就返回true,表示获取锁成功。如果锁已被获取,那么判断当前线程是否是持有锁的线程 (current == getExclusiveOwnerThread()),如果当前线程是持有锁的线程,那么执行锁重入的逻辑,然后方法返回true,表示获取锁成功。否则返回false,表示获取锁失败。

在这里插入图片描述

ReentrantLock的lock方法到这里就介绍完了。

在这里插入图片描述

ReentrantLock#unlock方法

接下来看一下ReentrantLock的unlock方法的具体逻辑。

    public void unlock() {sync.release(1);}

ReentrantLock的unlock方法调用了sync的release方法,这个和我们上一篇文章的例子是一样的。

AQS的release方法会调用tryRelease方法。因为释放锁的操作不需要区分公平还是非公平,都是一样的释放锁的逻辑,所以tryRelease方法就放到了Sync抽象类的内部。也就是说,FairSync和NonfairSync的tryRelease是同一个方法,都是Sync的tryRelease方法。

        protected final boolean tryRelease(int releases) {int c = getState() - releases;// 如果当前线程不是持有锁的线程,抛异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// c == 0,表示锁被释放,free设为true表示锁被释放,设置持有锁的线程为nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}// 更新statesetState(c);return free;}

Sync的tryRelease方法首先会调用AQS的getState方法获取state变量,然后计算state变量减去参数releases之后的值c。然后判断如果c等于0,表示锁被释放了,那么设置返回值free为true表示锁被释放,然后setExclusiveOwnerThread(null)设置持有锁的线程为null。然后不管c是否等于0,最后都会调用AQS的setState方法,更新state变量,最后返回free。

如果返回的free为true,表示锁被释放了,如果返回的free为false,表示锁还没有被释放。比如当前线程重复获取锁5次,现在调用了unlock方法3次,那么state等于2,锁还没有被释放,它需要再调用两次,state才等于0,此时锁才被真正释放。

在这里插入图片描述

到这里ReentrantLock的unlock方法也介绍完了。

ReentrantLock的其他方法简介

ReentrantLock不仅仅只有lock和unlock两个方法,还有其他获取和释放锁的方法,下面做一个简单介绍,不做深入的描述。

ReentrantLock#lockInterruptibly

    public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}

ReentrantLock的lockInterruptibly是以响应中断的方式获取锁,就是说在获取锁的过程中如果其他线程调用了thread.interrupt()方法打断了当前线程,那么当前线程会响应中断,不再继续获取锁。而我们上面介绍的ReentrantLock#lock方法,是不响应中断的,也就是说其他线程调用了当前线程的thread.interrupt()方法,当前线程也不做任何响应。

ReentrantLock#tryLock

    public boolean tryLock() {return sync.nonfairTryAcquire(1);}

ReentrantLock的tryLock方法是尝试获取锁,也就是尝试获取一下,成就成,不成就走,也不会阻塞。

ReentrantLock#tryLock(带参数)

    public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}

ReentrantLock还有一个带参数的tryLock方法 boolean tryLock(long timeout, TimeUnit unit),该方法和无参的tryLock方法一样,也是尝试获取锁,成就成,不成不会马上走,而是阻塞等待一段时间。timeout参数就是指定阻塞等待的时间,unit则是时间单位。

ReentrantLock#newCondition

    public Condition newCondition() {return sync.newCondition();}

ReentrantLock的newCondition可以返回一个Condition对象,Condition 可以实现类似于和synchronized加Object#wait的功能,这个我们后面的文章再介绍吧。

在这里插入图片描述

相关文章:

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock&#xff0c;看完可以吊打面试官 ReentrantLock是什么&#xff0c;有什么作用ReentrantLock的使用ReentrantLock源码解析ReentrantLock#lock方法FairSync#tryAcquire方法NonfairSync#tryAcquire方法 Reentrant…...

【计算机网络】网络基础(上)

文章目录 1. 网络发展认识协议 2.网络协议初识协议分层OSI七层模型 | TCP/IP网络传输基本流程情况1&#xff1a;同一个局域网(子网)数据在两台通信机器中如何流转协议报头的理解局域网通信原理(故事版本)一般原理数据碰撞结论 情况2&#xff1a;跨一个路由器的两个子网IP地址与…...

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验四 蜂鸣器

目录 前言 一、原理图及知识点介绍 1.1、蜂鸣器原理图&#xff1a; 二、代码分析 前言 第一个实验:51单片机&#xff08;普中HC6800-EM3 V3.0&#xff09;实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验:51单片机&#xff08;普中HC6800-EM…...

无向图-已知根节点求高度

深搜板子题&#xff0c;无向图&#xff0c;加边加两个&#xff0c;dfs输入两个参数变量&#xff0c;一个是当前深搜节点&#xff0c;另一个是父节点&#xff08;避免重复搜索父节点&#xff09;&#xff0c;恢复现场 ///首先完成数组模拟邻接表#include<iostream> #incl…...

RIP动态路由协议 (已过时,逐渐退出舞台)

RIP 路由更新&#xff1a;RIP1/2 每30秒钟广播(255.255.255.255)/组播 &#xff08;224.0.0.9&#xff09;一次超时&#xff1a;180秒未收到更新&#xff0c;即标记为不可用&#xff08;跳数16&#xff09;&#xff0c;240秒收不到&#xff0c;即从路由表中删除 &#xff1b;跳…...

C++ operator关键字的使用(重载运算符、仿函数、类型转换操作符)

目录 定义operator重载运算符operator重载函数调用运算符operator类型转换操作符 定义 C11 中&#xff0c;operator 是一个关键字&#xff0c;用于重载运算符。通过重载运算符&#xff0c;您可以定义自定义类型的对象在使用内置运算符时的行为。 operator重载用法一般可以分为…...

深度学习笔记-暂退法(Drop out)

背景 在机器学习的模型中&#xff0c;如果模型的参数太多&#xff0c;而训练样本又太少&#xff0c;训练出来的模型很容易产生过拟合的现象。在训练神经网络的时候经常会遇到过拟合的问题&#xff0c;过拟合具体表现在&#xff1a;模型在训练数据上损失函数较小&#xff0c;预…...

使用自适应去噪在线顺序极限学习机预测飞机发动机剩余使用寿命(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

实验5-7 使用函数求1到10的阶乘和 (10 分)

实验5-7 使用函数求1到10的阶乘和 &#xff08;10 分&#xff09; 本题要求实现一个计算非负整数阶乘的简单函数&#xff0c;使得可以利用该函数&#xff0c;计算1!2!⋯10!的值。 函数接口定义&#xff1a; double fact( int n ); 其中n是用户传入的参数&#xff0c;其值不超过…...

kafka部署

1.kafka安装部署 1.1 kafaka下载 https://archive.apache.org/dist/kafka/2.4.0/kafka_2.12-2.4.0.tgz Binary downloads是指预编译的软件包,可供直接下载和安装,无需手动编译。在计算机领域中,二进制下载通常指预构建的软件分发包,可以直接安装在系统上并使用 "2.…...

Spring Security6入门及自定义登录

一、前言 Spring Security已经更新到了6.x,通过本专栏记录以下Spring Security6学习过程&#xff0c;当然大家可参考Spring Security5专栏对比学习 Spring Security5专栏地址&#xff1a;security5 Spring Security是spring家族产品中的一个安全框架&#xff0c;核心功能包括…...

开放式蓝牙耳机哪个品牌好用?盘点几款很不错的开放式耳机

​相比传统入耳式耳机&#xff0c;开放式耳机因其不入耳不伤耳的开放设计&#xff0c;不仅带来了舒适的佩戴体验&#xff0c;还创造了一种与周围环境互动的全新方式&#xff0c;户外运动过程时也无需担心发生事故&#xff0c;安全性更高。我整理了几款比较好用的开放式耳机给大…...

WebGL: 几个入门小例子

本文罗列几个WebGL入门例子&#xff0c;用于帮助WebGL学习。 一、概述 WebGL (Web Graphics Library)是一组基于Open ES、在Web内渲染3D图形的Javascript APIs。 Ref. from Khronos Group: WebGL WebGL™ is a cross-platform, royalty-free open web standard for a low-lev…...

PAT(Advanced Level)刷题指南 —— 第一弹

一、1001 A+B Format 1. 问题重述 给两个整数,输出这两个数的加和的结果,每三位用逗号分隔。 2. Sample Input -1000000 93. Sample Output -999,9914. 题解 思路:直接将两个整数相加,判断是否为负,是负数则直接输出负号并转为正数;然后将正数转为字符串,按规则每…...

【BASH】回顾与知识点梳理(九)

【BASH】回顾与知识点梳理 九 九. 扩展正则表达式(延伸正规表示法)9.1 egrep命令语法匹配指定模式的行(用法和grep相同)忽略大小写匹配(用法和grep相同)反向匹配(用法和grep相同)显示行号(用法和grep相同)递归搜索目录(用法和grep相同)匹配整词(用法和grep相同)统计匹配行数(用…...

Android 版本 对应的 API版本

Android 14&#xff08;开发者预览版&#xff09; 如需详细了解平台变更&#xff0c;请参阅 Android 14 文档。 Android 13&#xff08;API 级别 33&#xff09; 如需详细了解平台变更&#xff0c;请参阅 Android 13 文档。 Android 12&#xff08;API 级别 31、32&#xf…...

Django 异常信息 E302 expected 2 blank lines, found 1

在Django中&#xff0c;PEP 8风格指南建议在任何类定义之前都应该有两个空白行&#xff0c;包括视图&#xff08;views&#xff09;。错误信息"E302 expected 2 blank lines, found 1"表示在类定义之前只有一个空白行&#xff0c;而Django希望有两个空白行。 要修复…...

2019年09月《全国青少年软件编程等级考试》Python一级真题解析

一、单选题 第1题 关于Python的编程环境&#xff0c;下列的哪个表述是正确的&#xff1f; A&#xff1a;Python的编程环境是图形化的&#xff1b; B&#xff1a;Python只有一种编程环境ipython&#xff1b; C&#xff1a;Python自带的编程环境是IDLE&#xff1b; D&#…...

mybatis如何防止SQL注入

阅读正文&#xff1a;​​​​​​​ mybatis是如何防止SQL注入的 1、首先看一下下面两个sql语句的区别&#xff1a; <select id"selectByNameAndPassword" parameterType"java.util.Map" resultMap"BaseResultMap"> select id, usernam…...

DoIP学习笔记系列:(三)用CAPL脚本过“安全认证”,$27服务实现

文章目录 1. 如何调用接口通过安全认证?如何新建CAPL工程,在此不再赘述,本章主要分享一下如何在CAPL中调用DoIP接口、diag接口进行DoIP和诊断的测试。 注意:CANoe工具本身的使用没什么难的,所谓会者不难难者不会,各位小伙伴有疑问要多问,多交流,往往难事都只是一层窗户…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...

JS红宝书笔记 - 3.3 变量

要定义变量&#xff0c;可以使用var操作符&#xff0c;后跟变量名 ES实现变量初始化&#xff0c;因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符&#xff0c;可以创建一个全局变量 如果需要定义…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用

阻止除自定义标签之外的所有标签 先输入一些标签测试&#xff0c;说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时&#xff08;如通过点击或键盘导航&…...