美团一面:说说synchronized的实现原理?问麻了。。。。
引言
在现代软件开发领域,多线程并发编程已经成为提高系统性能、提升用户体验的重要手段。然而,多线程环境下的数据同步与资源共享问题也随之而来,处理不当可能导致数据不一致、死锁等各种并发问题。为此,Java语言提供了一种内置的同步机制——synchronized
关键字,它能够有效地解决并发控制的问题,确保共享资源在同一时间只能由一个线程访问,从而维护程序的正确性与一致性。
synchronized
作为Java并发编程的基础构建块,其简洁易用的语法形式背后蕴含着复杂的底层实现原理和技术细节。深入理解synchronized
的运行机制,不仅有助于我们更好地利用这一特性编写出高效且安全的并发程序,同时也有利于我们在面对复杂并发场景时,做出更为明智的设计决策和优化策略。
本文将从synchronized
的基本概念出发,逐步剖析其内在的工作机制,探讨诸如监视器(Monitor)等关键技术点,并结合实际应用场景来展示synchronized
的实际效果和最佳实践。通过对synchronized
底层实现原理的深度解读,旨在为大家揭示Java并发世界的一隅,提升对并发编程的认知高度和实战能力。
synchronized是什么?
synchronized
是Java中实现线程同步的关键字,主要用于保护共享资源的访问,确保在多线程环境中同一时间只有一个线程能够访问特定的代码段或方法。它提供了互斥性和可见性两个重要特性,确保了线程间操作的原子性和数据的一致性。
synchronized的特性
synchronized
关键字具有三个基本特性,分别是互斥性、可见性和有序性。
互斥性
synchronized
关键字确保了在其控制范围内的代码在同一时间只能被一个线程执行,实现了资源的互斥访问。当一个线程进入了synchronized
代码块或方法时,其他试图进入该同步区域的线程必须等待,直至拥有锁的线程执行完毕并释放锁。
可见性
synchronized
还确保了线程间的数据可见性。一旦一个线程在synchronized
块中修改了共享变量的值,其他随后进入同步区域的线程可以看到这个更改。这是因为synchronized
的解锁过程包含了将工作内存中的最新值刷新回主内存的操作,而加锁过程则会强制从主内存中重新加载变量的值。
有序性
synchronized
提供的第三个特性是有序性,它可以确保在多线程环境下,对于同一个锁的解锁操作总是先行于随后对同一个锁的加锁操作。这就意味着,通过synchronized
建立起了线程之间的内存操作顺序关系,有效地解决了由于编译器和处理器优化可能带来的指令重排序问题。
synchronized可以实现哪锁?
有上述synchronized的特性,我们可以知道synchronized可以实现这些锁:
- 可重入锁(Reentrant Lock):
synchronized
实现的锁是可重入的,这意味着同一个线程可以多次获取同一个锁,而不会被阻塞。这种锁机制允许线程在持有锁的情况下再次获取相同的锁,避免了死锁的发生。 - 排它锁/互斥锁/独占锁:
synchronized
实现的锁是互斥的,也就是说,在同一时间只有一个线程能够获取到锁,其他线程必须等待该线程释放锁才能继续执行。这确保了同一时刻只有一个线程可以访问被锁定的代码块或方法,从而保证了数据的一致性和完整性。 - 悲观锁:
synchronized
实现的锁属于悲观锁,因为它默认情况下假设会发生竞争,并且会导致其他线程阻塞,直到持有锁的线程释放锁。悲观锁的特点是对并发访问持保守态度,认为会有其他线程来竞争共享资源,因此在访问共享资源之前会先获取锁。 - 非公平锁:
synchronized
在早期的Java版本中,默认实现的是非公平锁,也就是说,线程获取锁的顺序并不一定按照它们请求锁的顺序来进行,而是允许“插队”,即已经在等待队列中的线程可能被后来请求锁的线程抢占。
有关Java中的锁的分类,请参考:阿里二面:Java中锁的分类有哪些?你能说全吗?
synchronized使用方式
synchronized
关键字可以修饰方法、代码块或静态方法,用于确保同一时间只有一个线程可以访问被synchronized
修饰的代码片段。
修饰实例方法
当synchronized
修饰实例方法时,锁住的是当前实例对象(this)。这意味着在同一时刻,只能有一个线程访问此方法,所有对该对象实例的其他同步方法调用将会被阻塞,直到该线程释放锁。
public class SynchronizedInstanceMethod implements Runnable{private static int counter = 0;// 修饰实例方法,锁住的是当前实例对象private synchronized void add() {counter++;}@Overridepublic void run() {for (int i = 0; i < 1000; i++) {add();}}public static void main(String[] args) throws Exception {SynchronizedInstanceMethod sim = new SynchronizedInstanceMethod();Thread t1 = new Thread(sim);Thread t2 = new Thread(sim);t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter value: " + counter);}
}
像上述这个例子,大家在接触多线程时一定会看过或者写过类似的代码,i++
在多线程的情况下是线程不安全的,所以我们使用synchronized
作用在累加的方法上,使其变成线程安全的。上述打印结果为:
Final block counter value: 2000
而对于synchronized
作用于实例方法上时,锁的是当前实例对象,但是如果我们锁住的是不同的示例对象,那么synchronized
就不能保证线程安全了。如下代码:
public class SynchronizedInstanceMethod implements Runnable{private static int counter = 0;// 修饰实例方法,锁住的是当前实例对象private synchronized void add() {counter++;}@Overridepublic void run() {for (int i = 0; i < 1000; i++) {add();}}public static void main(String[] args) throws Exception {Thread t1 = new Thread(new SynchronizedInstanceMethod());Thread t2 = new Thread(new SynchronizedInstanceMethod());t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter value: " + counter);}
}
执行结果为:
Final counter value: 1491
修饰静态方法
若synchronized
修饰的是静态方法,那么锁住的是类的Class对象,因此,无论多少个该类的实例存在,同一时刻也只有一个线程能够访问此静态同步方法。针对修饰实例方法的线程不安全的示例,我们只需要在synchronized
修饰的实例方法上加上static
,将其变成静态方法,此时synchronized
锁住的就是类的class对象。
public class SynchronizedStaticMethod implements Runnable{private static int counter = 0;// 修饰实例方法,锁住的是当前实例对象private static synchronized void add() {counter++;}@Overridepublic void run() {for (int i = 0; i < 1000; i++) {add();}}public static void main(String[] args) throws Exception {Thread t1 = new Thread(new SynchronizedStaticMethod());Thread t2 = new Thread(new SynchronizedStaticMethod());t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter value: " + counter);}
}
执行结果为:
Final counter value: 2000
修饰代码块
通过指定对象作为锁,可以更精确地控制同步范围。这种方式允许在一个方法内部对不同对象进行不同的同步控制。可以指定一个对象作为锁,只有持有该对象锁的线程才能执行被synchronized
修饰的代码块。
public class SynchronizedBlock implements Runnable{private static int counter = 0;@Overridepublic void run() {// 这个this还可以是SynchronizedBlock.class,说明锁住的是class对象synchronized (this){for (int i = 0; i < 1000; i++) {counter++;}}}public static void main(String[] args) throws Exception {SynchronizedBlock block = new SynchronizedBlock();Thread t1 = new Thread(block);Thread t2 = new Thread(block);t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter value: " + counter);}
}
synchronized
内置锁作为一种对象级别的同步机制,其作用在于确保临界资源的互斥访问,实现线程安全。它本质上锁定的是对象的监视器(Object Monitor),而非具体的引用变量。这种锁具有可重入性,即同一个线程在已经持有某对象锁的情况下,仍能再次获取该对象的锁,这显著增强了线程安全代码的编写便利性,并在一定程度上有助于降低因线程交互引起的死锁风险。
关于如何避免死锁,请参考:阿里二面:如何定位&避免死锁?连着两个面试问到了!
synchronized的底层原理
在JDK 1.6之前,synchronized
关键字所实现的锁机制确实被认为是重量级锁。这是因为早期版本的Java中,synchronized的实现依赖于操作系统的互斥量(Mutexes)来实现线程间的同步,这涉及到了从用户态到内核态的切换以及线程上下文切换等相对昂贵的操作。一旦一个线程获得了锁,其他试图获取相同锁的线程将会被阻塞,这种阻塞操作会导致线程状态的改变和CPU资源的消耗,因此在高并发、低锁竞争的情况下,这种锁机制可能会成为性能瓶颈。
而在JDK 1.6中,对synchronized进行了大量优化,其中包括引入了偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)的概念。接下来我们先说一下JDK1.6之前synchronized
的原理。
对象的组成结构
在JDK1.6之前,在Java虚拟机中,Java对象的内存结构主要有对象头(Object Header),实例数据(Instance Data),对齐填充(Padding) 三个部分组成。
-
对象头(Object Header):
对象头主要包含了两部分信息:Mark Word(标记字段)和指向类元数据(Class Metadata)的指针。Mark Word 包含了一些重要的标记信息,比如对象是否被锁定、对象的哈希码、GC相关信息等。类元数据指针指向对象的类元数据,用于确定对象的类型信息、方法信息等。 -
实例数据(Instance Data):
实例数据是对象的成员变量和实例方法所占用的内存空间,它们按照声明的顺序依次存储在对象的实例数据区域中。实例数据包括对象的所有非静态成员变量和非静态方法。 -
填充(Padding):
在JDK 1.6及之前的版本中,为了保证对象在内存中的存储地址是8字节的整数倍,可能会在对象的实例数据之后添加一些填充字节。这些填充字节的目的是对齐内存地址,提高内存访问效率。填充字节通常不包含任何实际数据,只是用于占位。
对象头
在JDK 1.6之前的Java HotSpot虚拟机中,对象头的基本组成依然包含Mark Word和类型指针(Klass Pointer),但当时对于锁的实现还没有引入偏向锁和轻量级锁的概念,因此对象头中的Mark Word在处理锁状态时比较简单,主要是用来存储锁的状态信息以及与垃圾收集相关的数据。在一个32位系统重对象头大小通常约为32位,而在64位系统中大小通常为64位。
对象头组成部分:
- Mark Word(标记字):
在早期版本的HotSpot虚拟机中,Mark Word主要存储的信息包括:
- 对象的hashCode(在没有锁定时)。
- 对象的分代年龄(用于垃圾回收算法)。
- 锁状态信息,如无锁、重量级锁状态(在使用
synchronized
关键字时)。 - 对象的锁指针(Monitor地址,当对象被重量级锁锁定时,存储的是指向重量级锁(Monitor)的指针)。
对象头中的Mark Word是一个非固定的数据结构,它会根据对象的状态复用自己的存储空间,存储不同的数据。在Java HotSpot虚拟机中,Mark Word会随着程序运行和对象状态的变化而存储不同的信息。其信息变化如下:
从存储信息的变化可以看出:
- 对象头的最后两位存储了锁的标志位,01表示初始状态,即未加锁。此时,对象头内存储的是对象自身的哈希码。无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。
- 当进入偏向锁阶段时,对象头内的标志位变为01,并且存储当前持有锁的线程ID。这意味着只有第一个获取锁的线程才能继续持有锁,其他线程不能竞争同一把锁。
- 在轻量级锁阶段,标志位变为00,对象头内存储的是指向线程栈中锁记录的指针。这种情况下,多个线程可以通过比较锁记录的地址与对象头内的指针地址来确定自己是否拥有锁。
其中轻量级锁和偏向锁是Java 6 对 synchronized 锁进行优化后新增加的。重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。
-
类型指针(Klass Pointer 或 Class Pointer):
类型指针指向对象的类元数据(Class Metadata),即对象属于哪个类的类型信息,用于确定对象的方法表和字段布局等。在一个32位系统重大小通常约为32位,而在64位系统中大小通常为64位。 -
数组长度(Array Length)(仅对数组对象适用):
如果对象是一个数组,对象头中会额外包含一个字段来存储数组的长度。在一个32位系统中大小通常约为32位,而在64位系统中大小通常为64位。
监视器(Monitor)
在Java中,每个对象都与一个Monitor关联,Monitor是一种同步机制,负责管理线程对共享资源的访问权限。当一个Monitor被线程持有时,对象便处于锁定状态。Java的synchronized
关键字在JVM层面上通过MonitorEnter
和MonitorExit
指令实现方法同步和代码块同步。MonitorEnter
尝试获取对象的Monitor所有权(即获取对象锁),MonitorExit
确保每个MonitorEnter操作都有对应的释放操作。
在HotSpot虚拟机中,Monitor具体由ObjectMonitor实现,其结构如下:
ObjectMonitor() {_header = NULL;_count = 0; //锁计数器,表示重入次数,每当线程获取锁时加1,释放时减1。_waiters = 0, //等待线程总数,不一定在实际的ObjectMonitor中有直接体现,但在管理线程同步时是一个重要指标。_recursions = 0; //与_count类似,表示当前持有锁的线程对锁的重入次数。_object = NULL; // 通常指向关联的Java对象,即当前Monitor所保护的对象。_owner = NULL; // 持有ObjectMonitor对象的线程地址,即当前持有锁的线程。_WaitSet = NULL; //存储那些调用过`wait()`方法并等待被唤醒的线程队列。_WaitSetLock = 0 ; // 用于保护_WaitSet的锁。_Responsible = NULL ;_succ = NULL ;_cxq = NULL ; //阻塞在EntryList上的单向线程列表,可能用于表示自旋等待队列或轻量级锁的自旋链表。FreeNext = NULL ; // 在对象Monitor池中可能用于链接空闲的ObjectMonitor对象。_EntryList = NULL ; // 等待锁的线程队列,当线程请求锁但发现锁已被持有时,会被放置在此队列中等待。_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ; // 标志位,可能用于标识_owner是否指向一个真实的线程对象。}
其中最重要的就是_owner
、_WaitSet
、_EntryList
和count
几个字段,他们之间的转换关系:
-
_owner
:
当一个线程首次成功执行synchronized
代码块或方法时,会尝试获取对象的Monitor(即ObjectMonitor
),并将自身设置为_owner
。该线程此刻拥有了对象的锁,可以独占访问受保护的资源。 -
_EntryList
→_owner
:
当多个线程同时尝试获取锁时,除第一个成功获取锁的线程外,其余线程会进入_EntryList
排队等待。一旦_owner
线程释放锁,_EntryList
中的下一个线程将有机会获取锁并成为新的_owner
。 -
_owner
→_WaitSet
:
当_owner
线程在持有锁的情况下调用wait()
方法时,它会释放锁(即_owner
置为NULL
),并把自己从_owner
转变为等待状态,然后将自己添加到_WaitSet
中。这时,线程进入等待状态,暂停执行,等待其他线程通过notify()
或notifyAll()
唤醒。 -
_WaitSet
→_EntryList
:
当其他线程调用notify()
或notifyAll()
方法时,会选择一个或全部在_WaitSet
中的线程,将它们从_WaitSet
移除,并重新加入到_EntryList
中。这样,这些线程就有机会再次尝试获取锁并成为新的_owner
。
有上述转换关系我们可以发现,当多线程访问同步代码时:
- 线程首先尝试进入_EntryList竞争锁,成功获取Monitor后,将_owner设置为当前线程并将count递增。
- 若线程调用wait()方法,会释放Monitor、清空_owner,并将线程移到_WaitSet中等待被唤醒。
- 当线程执行完毕或调用notify()/notifyAll()唤醒等待线程后,会释放Monitor,使得其他线程有机会获取锁。
在Java对象的对象头(Mark Word)中,存储了与锁相关的状态信息,这使得任意Java对象都能作为锁来使用,同时,notify/notifyAll/wait等方法正是基于Monitor锁对象来实现的,因此这些方法必须在synchronized
代码块中调用。
我们查看上述同步代码块SynchronizedBlock
的字节码文件:
从上述字节码中可以看到同步代码块的实现是由monitorenter
和monitorexit
指令完成的,其中monitorenter
指令所在的位置是同步代码块开始的位置,第一个monitorexit
指令是用于正常结束同步代码块的指令,第二个monitorexit
指令是用于异常结束时所执行的释放Monitor指令。
关于查看class文件的字节码文件,有两种方式:1、通过命令: javap -verbose <class路径>/class文件。2、IDEA中通过插件:
jclasslib Bytecode viewer
。
我们再看一下作用于同步方法的字节码:
我们可以看出同步方法上没有monitorenter
和 monitorexit
这两个指令了,而在查看该方法的class文件的结构信息时发现了Access flags
后边的synchronized标识,该标识表明了该方法是一个同步方法。Java虚拟机通过该标识可以来辨别一个方法是否为同步方法,如果有该标识,线程将持有Monitor,在执行方法,最后释放Monitor。
总结
synchronized
作用于同步代码块时的原理:
Java虚拟机使用monitorenter和monitorexit指令实现同步块的同步。monitorenter指令在进入同步代码块时执行,尝试获取对象的Monitor(即锁),monitorexit指令在退出同步代码块时执行,释放Monitor。
而对于方法级别的同步的原理:
Java虚拟机通过在方法的访问标志(Access flags)中设置ACC_SYNCHRONIZED标志来实现方法同步。当一个方法被声明为synchronized
时,编译器会在生成的字节码中插入monitorenter和monitorexit指令,确保在方法执行前后正确地获取和释放对象的Monitor。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等。
相关文章:

美团一面:说说synchronized的实现原理?问麻了。。。。
引言 在现代软件开发领域,多线程并发编程已经成为提高系统性能、提升用户体验的重要手段。然而,多线程环境下的数据同步与资源共享问题也随之而来,处理不当可能导致数据不一致、死锁等各种并发问题。为此,Java语言提供了一种内置…...

P1123 取数游戏(dfs算法)
题目描述 一个 NM 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻 8个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。 输入格式 第…...

交叉验证(Cross-Validation)
交叉验证的基本概念 交叉验证通常用于评估机器学习模型在未知数据上的性能。它将数据集分成k个不同的子集,然后进行k次训练和验证。在每次迭代中,选择一个子集作为测试集,其余的子集作为训练集。这样,每个子集都用作过测试集&…...

【kears】(01)keras使用介绍
文章目录 一.特点二.keras如何支持TensorFlow、CNTK 和 Theano2.1 使用 TensorFlow 后端引擎训练和评估模型2.2 使用 TensorFlow 后端引擎训练和评估模型2.3 使用 Theano后端引擎训练和评估模型2.4 不同深度学习框架如何选择1.1 keras.datasets:包含多种常用数据集1…...

2. TypeScript 安装与环境配置指南
TypeScript 是 JavaScript 的一个超集,它为 JavaScript 增加了类型系统和对 ES6 的支持。TypeScript 不仅能够帮助开发者捕获代码中的错误,还能提供更好的编辑器支持,包括代码补全、接口提示等。本文将详细介绍如何在您的开发环境中安装和配置…...

python pygame库的略学
文章目录 概述1. pygame的初始化和退出2. 创建游戏窗口(1)set_mode()(2)set_capyion()(3)update() 3. 游戏循坏与游戏时钟4. 图形和文本绘制(1)图形绘制(2)文…...

大模型日报2024-04-09
大模型日报 2024-04-09 大模型资讯 苹果预告超越ChatGPT的新AI模型ReaLM 摘要: 苹果公司最新宣布,即将推出一款名为ReaLM的人工智能模型。这款AI技术在理解复杂屏幕用户指令方面表现出高超的能力,并能与用户进行自然流畅的对话。ReaLM的推出预示着苹果在…...

抖音视频如何下载保存(方法分享)
有时刷抖音视频,看的喜欢的视频想要下载到本地,但是有很多视频无法下载或者下载下来是有水印的,那怎么办呢? 抖音视频下载有两种情况: 一种是可以直接点击分享下载,然后可以直接点击保存到相册。 视频就自动下载…...

MySQL-用户与权限管理:用户管理、权限管理、角色管理
用户与权限管理 用户与权限管理1.用户管理1.1 登录MySQL服务器1.2 创建用户1.3 修改用户1.4 删除用户1.5 设置当前用户密码1.6 修改其它用户密码 2. 权限管理2.1 权限列表2.2 授予权限的原则2.3 授予权限2.4 查看权限2.5 收回权限 访问控制连接核实阶段请求核实阶段 3. 角色管理…...

Vue.js中如何使用Vue Router处理浏览器返回键的功能
在Vue.js中,Vue Router默认提供了处理浏览器返回键的功能。当用户点击浏览器的返回键时,Vue Router会自动导航到历史记录中的上一个路由。然而,如果你想自定义返回键的行为或者在特定的页面上进行特殊处理,你可以使用Vue Router的…...

QT drawPixmap和drawImage处理图片模糊问题
drawPixmap和drawImage显示图片时,如果图片存在缩放时,会出现模糊现象,例如将一个100x100 的图片显示到30x30的区域,这个时候就会出现模糊。如下: 实际图片: 这个问题就是大图显示成小图造成的像素失真。 当…...

YOLOv9改进策略 :小目标 | 新颖的多尺度前馈网络(MSFN) | 2024年4月最新成果
💡💡💡本文独家改进:多尺度前馈网络(MSFN),通过提取不同尺度的特征来增强特征提取能力,2024年最新的改进思路 💡💡💡创新点:多尺度前馈网络创新十足,抢先使用 💡💡💡如何跟YOLOv8结合:1)放在backbone后增强对全局和局部特征的提取能力;2)放在detect…...

从零开始:一步步学习爬虫技术的实用指南(一)
从零开始:一步步学习爬虫技术的实用指南(一) Urllib1.什么是互联网爬虫2.爬虫核心3.爬虫的用途4.爬虫的分类4.1 通用爬虫:4.1 聚焦爬虫: 5.反爬手段5.1 User‐Agent:5.2.代理IP5.3.验证码访问5.4.动态加载网…...

Python面向对象详解
文章目录 类和继承变量保护类装饰器 类和继承 Python虽然以函数式著称,但在Python中,万物皆对象,其对面向对象编程是有着非常不错的支持的。类是面向对象的核心数据类型,下面代码就创建了一个Person类。 class Person:count 0d…...

思维题锻炼-最小数字
思维题锻炼-最小数字 目录题目描述输入样例输出样例代码 目录 题目描述 给一串数字,求出最小的整数,不能是原数字串中的数字,也不能由数字串中的数字相加得到 输入样例 5 2 1输出样例 4代码 #include<bits/stdc.h> #include<s…...

ubuntu20.04 运行 lio-sam 流程记录
ubuntu20.04 运行 lio-sam 一、安装和编译1.1、安装 ROS11.2、安装 gtsam1.3、安装依赖1.4、下载源码1.5、修改文件1.6、编译和运行 二、官方数据集的运行2.1、casual_walk_2.bag2.2、outdoor.bag、west.bag2.3、park.bag 三、一些比较好的参考链接 记录流程,方便自…...
P5356 [Ynoi2017] 由乃打扑克
我手把手教她打扑克 qwq 综合分析一下2个操作,查找区间第k小的值,感觉可以用主席树,区间修改那没事了 考虑分块做法,块长B 分析第一个操作 只需要维护数列的单调性,然后二分答案上二分就ok了 分析第二个操作 维护一个加法懒…...

随机潮流应对不确定性?计及分布式发电的配电系统随机潮流计算程序代码!
前言 随着分布式电源在电力系统中所占比例的不断扩大,研究分布式发电对系统稳态运行的影响势在必行。带分布式发电的潮流计算常常用来评估其并网后对系统的影响,同时它也是分析分布式发电对电网稳定性的影响等其他理论研究工作的基础。然而,许多分布式发…...

Oracle表空间满清理方案汇总分享
目录 前言思考 一、第一种增加表空间的数据文件数量达到总容量的提升 二、第二种解决方案针对system和sysaux的操作 2.1SYSTEM表空间优化 2.2sysaux表空间回收 2.2.1针对sysaux的表空间爆满还有第二套方案维护 三、第三种解决方案使用alter tablespace resize更改表空间的…...

基于单片机数码管20V电压表仿真设计
**单片机设计介绍,基于单片机数码管20V电压表仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机数码管20V电压表仿真设计的主要目的是通过单片机和数码管显示电路实现一个能够测量0到20V直流电压的电…...

SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测
SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型…...

C++——优先级队列
前言:这篇文章我们继续来分享一个c的容器——优先级队列。 一.理解优先级 何为优先级一说?实际上就是有顺序的意思。 优先级队列,即有顺序的队列,是一个无需我们自己进行排序操作,在数据传入时就会由容器自己排好序的…...

docker部署jumpserver
1、安装Docker以及相关依赖 配置yum源 sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin2、添加国…...

ARM FVP平台的terminal窗口大小如何设置
当启动ARM FVP平台时,terminal窗口太小怎么办?看起来非常累眼睛,本博客来解决这个问题。 首先看下ARM FVP平台对Host主机的需求: 通过上图可知,UART默认使用的是xterm。因此,我们需要修改xterm的默认字体设…...

003 静态代理
文章目录 StudentServiceImplStudentService.javaStudentServiceProxy.javaStudentServiceProxy1.javaStudentServiceProxyTest.java StudentServiceImpl package com.aistart.service.impl;import com.aistart.mapper.StudentMapper; import com.aistart.pojo.Student; import…...

基于JAX的二阶优化方法的实践
使用协作分支上的算法 git clone https://github.com/linjing-lab/jax.git cd jax git checkout linjing-lab cd examples在命令行预览方法 牛顿方法: cat newton_method.py拟牛顿法: cat bfgs_method.py在命令行运行程序 python newton_method.pyp…...

【计算机考研】408算法大题怎么练?
先说结论:基础阶段学好各个数据结构与,重点是数组、链表、树、图。然后强化阶段突破算法提 在基础阶段,并不需要过于专门地练习算法。相反,基础阶段的重点应该放在对各种数据结构原理的深入理解上。在我个人的经验中,…...

输入框验证数字类型
校验大于0的数,且小数点后最多为八位小数 let k /^(?!0(\.0)?$)\d(\.\d{1,8})?$/; console.log(k.test(0.00000001)); // true console.log(k.test(0.00000000)); // false console.log(k.test(0.12)); // true console.log(k.test(12.12)); // true输入0-1的数字…...

LeetCode 377——组合总和 Ⅳ
阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此题一看应该就是需要用到动态规划算法,假设我们以 f[d]表示总和为 d 的元素组合的个数,首先,我们遍历 nums 数组, 如果有 nums[i] < target,那么组…...

ubuntu同步网络时间
安装ntpdate sudo apt-get update sudo apt-get install ntpdate设置系统时间与网络时间同步 sudo ntpdate cn.pool.ntp.org设置时区亚洲上海 sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime设置时间为24小时制 echo "LC_TIMEen_DK.UTF-8" >>/…...