锁升级之Synchronized
Synchronized JVM系统锁
一个对象里如果有多个synchronized方法,同一时刻,只要有一个线程去调用其中的一个synchronized方法,其他线程只能等待!锁的是当前对象,对象被锁定后,其他线程都不能访问当前对象的其他synchronized方法。
非静态同步方法,用的是同一把锁!
当一个线程视图访问同步代码块时,首先必须获得锁,退出或者抛出异常时,释放锁!
特性:原子、可重入、有序、可见、不可中断
synchronized 实现原理
前言
synchronized 锁在 Java 中经常使用它的源码是 C++ 实现的,它的实现原理是怎样的呢?本文以 OpenJDK8 为例探究以下内容。
synchronized 是如何工作的
synchronized 锁升级过程
重量级锁的队列之间协作过程和策略
对象头
对象头的内容非常多这里我们只做简单介绍以引出后文。在 JVM 中对象布局分为三块区域:
对象头
实例数据
对齐填充

Mark Word:HashCode、GC信息、锁信息
Klass Pointer: 类型指针指向它元数据的指针
当线程访问同步块时首先需要获得锁并把相关信息存储在对象头中。所以 wait、notify、notifyAll 这些方法为什么被设计在 Object 中或许你已经找到答案了。
Hotspot 有两种对象头:
数组类型,使用 arrayOopDesc 来描述对象头
其它,使用 instanceOopDesc 来描述对象头
对象头由两部分组成
Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。
Klass Pointer:类型指针指向它的类元数据的指针。
64 位虚拟机 Mark Word 是 64bit 其结构如下:

在 JDK 6 中虚拟机团队对锁进行了重要改进,优化了其性能引入了 偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化等实现,其中 锁消除和锁粗化本文不做详细讨论其余内容我们将对其进行逐一探究。
总体上来说锁状态升级流程如下:
无锁-->偏向锁-->轻量级-->重锁
偏向锁
流程
当线程访问同步块并获取锁时处理流程如下:
检查 mark word 的线程 id 。
如果为空则设置 CAS 替换当前线程 id。如果替换成功则获取锁成功,如果失败则撤销偏向锁。
如果不为空则检查 线程 id为是否为本线程。如果是则获取锁成功,如果失败则撤销偏向锁。
持有偏向锁的线程以后每次进入这个锁相关的同步块时,只需比对一下 mark word 的线程 id 是否为本线程,如果是则获取锁成功。
如果发生线程竞争发生 2、3 步失败的情况则需要撤销偏向锁。
偏向锁的撤销
偏向锁的撤销动作必须等待全局安全点
暂停拥有偏向锁的线程,判断对象是否处于被锁定状态
撤销偏向锁恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态
优点
只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。
缺点
如果存在竞争会带来额外的锁撤销操作。
轻量级锁
加锁
多个线程竞争偏向锁导致偏向锁升级为轻量级锁
JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)
线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。
解锁
使用 CAS 操作将 Mark Word 还原
如果第 1 步执行成功则释放完成
如果第 1 步执行失败则膨胀为重量级锁。
优点
其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。
缺点
在有多线程竞争的情况下轻量级锁增加了额外开销。
自旋锁
自旋是一种获取锁的机制并不是一个锁状态。在膨胀为重量级锁的过程中或重入时会多次尝试自旋获取锁以避免线程唤醒的开销,但是它会占用 CPU 的时间因此如果同步代码块执行时间很短自旋等待的效果就很好,反之则浪费了 CPU 资源。默认情况下自旋次数是 10 次用户可以使用参数 -XX : PreBlockSpin 来更改。那么如何优化来避免此情况发生呢?我们来看适应性自旋。
适应性自旋锁
JDK 6 引入了自适应自旋锁,意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果对于某个锁很少自旋成功那么以后有可能省略掉自旋过程以避免资源浪费。有了自适应自旋随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虛拟机就会变得越来越“聪明”了。
优点
竞争的线程不会阻塞挂起,提高了程序响应速度。避免重量级锁引起的性能消耗。
缺点
如果线程始终无法获取锁,自旋消耗 CPU 最终会膨胀为重量级锁。
重量级锁
在重量级锁中没有竞争到锁的对象会 park 被挂起,退出同步块时 unpark 唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。
ObjectMonitor 中包含一个同步队列(由 _cxq 和 _EntryList 组成)一个等待队列( _WaitSet )。
被notify或 notifyAll 唤醒时根据 policy 策略选择加入的队列(policy 默认为 0)
退出同步块时根据 QMode 策略来唤醒下一个线程(QMode 默认为 0)
这里稍微提及一下管程这个概念。synchronized 关键字及 wait、notify、notifyAll 这三个方法都是管程的组成部分。可以说管程就是一把解决并发问题的万能钥匙。有两大核心问题管程都是能够解决的:
互斥:即同一时刻只允许一个线程访问共享资源;
同步:即线程之间如何通信、协作。
synchronized 的 monitor锁机制和 JDK 并发包中的 AQS 是很相似的,只不过 AQS 中是一个同步队列多个等待队列。熟悉 AQS 的同学可以拿来做个对比。
队列协作流程图

源码分析
在 HotSpot 中 monitor 是由 ObjectMonitor 实现的。其源码是用 c++来实现的源文件是 ObjectMonitor.hpp 主要数据结构如下所示:
ObjectMonitor() {_header = NULL;_count = 0;_waiters = 0, // 等待中的线程数_recursions = 0; // 线程重入次数_object = NULL; // 存储该 monitor 的对象_owner = NULL; // 指向拥有该 monitor 的线程_WaitSet = NULL; // 等待线程 双向循环链表_WaitSet 指向第一个节点_WaitSetLock = 0 ;_Responsible = NULL ;_succ = NULL ;_cxq = NULL ; // 多线程竞争锁时的单向链表FreeNext = NULL ;_EntryList = NULL ; // _owner 从该双向循环链表中唤醒线程,_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0; // 前一个拥有此监视器的线程 ID}
_owner:初始时为 NULL。当有线程占有该 monitor 时 owner 标记为该线程的 ID。当线程释放 monitor 时 owner 恢复为 NULL。owner 是一个临界资源 JVM 是通过 CAS 操作来保证其线程安全的。
_cxq:竞争队列所有请求锁的线程首先会被放在这个队列中(单向)。_cxq 是一个临界资源 JVM 通过 CAS 原子指令来修改_cxq 队列。
每当有新来的节点入队,它的 next 指针总是指向之前队列的头节点,而_cxq 指针会指向该新入队的节点,所以是后来居上。
_EntryList: _cxq 队列中有资格成为候选资源的线程会被移动到该队列中。
_WaitSet: 等待队列因为调用 wait 方法而被阻塞的线程会被放在该队列中。
monitor 竞争过程
通过 CAS 尝试把 monitor 的 owner 字段设置为当前线程。
如果设置之前的 owner 指向当前线程,说明当前线程再次进入 monitor,即重入锁执行 recursions ++ ,记录重入的次数。
如果当前线程是第一次进入该 monitor, 设置 recursions 为 1,_owner 为当前线程,该线程成功获得锁并返回。
如果获取锁失败,则等待锁的释放。
执行 monitorenter 指令时 调用以下代码
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endifif (PrintBiasedLockingStatistics) {Atomic::inc(BiasedLocking::slow_path_entry_count_addr());}Handle h_obj(thread, elem->obj());assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");
// 是否使用偏向锁 JVM 启动时设置的偏向锁-XX:-UseBiasedLocking=false/trueif (UseBiasedLocking) {// Retry fast entry if bias is revoked to avoid unnecessary inflationObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);} else {// 轻量级锁ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);}assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),"must be NULL or an object");
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
slow_enter 方法主要是轻量级锁的一些操作,如果操作失败则会膨胀为重量级锁,过程前面已经描述比较清楚此处不在赘述。enter 方法则为重量级锁的入口源码如下
void ATTR ObjectMonitor::enter(TRAPS) {Thread * const Self = THREAD ;void * cur ;// 省略部分代码// 通过 CAS 操作尝试把 monitor 的_owner 字段设置为当前线程cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;if (cur == NULL) {assert (_recursions == 0 , "invariant") ;assert (_owner == Self, "invariant") ;return ;}// 线程重入,recursions++if (cur == Self) {_recursions ++ ;return ;}// 如果当前线程是第一次进入该 monitor, 设置_recursions 为 1,_owner 为当前线程if (Self->is_lock_owned ((address)cur)) {assert (_recursions == 0, "internal state error");_recursions = 1 ;_owner = Self ;OwnerIsThread = 1 ;return ;}for (;;) {jt->set_suspend_equivalent();// 如果获取锁失败,则等待锁的释放;EnterI (THREAD) ;if (!ExitSuspendEquivalent(jt)) break ;_recursions = 0 ;_succ = NULL ;exit (false, Self) ;jt->java_suspend_self();}Self->set_current_pending_monitor(NULL);}}
monitor 等待
当前线程被封装成 ObjectWaiter 对象 node,状态设置成ObjectWaiter::TS_CXQ。
for 循环通过 CAS 把 node 节点 push 到_cxq列表中,同一时刻可能有多个线程把自己的 node 节点 push 到_cxq列表中。
node 节点 push 到_cxq 列表之后,通过自旋尝试获取锁,如果还是没有获取到锁则通过 park 将当前线程挂起等待被唤醒。
当该线程被唤醒时会从挂起的点继续执行,通过ObjectMonitor::TryLock 尝试获取锁。
// 省略部分代码void ATTR ObjectMonitor::EnterI (TRAPS) {Thread * Self = THREAD ;assert (Self->is_Java_thread(), "invariant") ;assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;// Try lock 尝试获取锁if (TryLock (Self) > 0) {assert (_succ != Self , "invariant") ;assert (_owner == Self , "invariant") ;assert (_Responsible != Self , "invariant") ;// 如果获取成功则退出,避免 park unpark 系统调度的开销return ;}// 自旋获取锁if (TrySpin(Self) > 0) {assert (_owner == Self, "invariant");assert (_succ != Self, "invariant");assert (_Responsible != Self, "invariant");return;}// 当前线程被封装成 ObjectWaiter 对象node, 状态设置成 ObjectWaiter::TS_CXQObjectWaiter node(Self) ;Self->_ParkEvent->reset() ;node._prev = (ObjectWaiter *) 0xBAD ;node.TState = ObjectWaiter::TS_CXQ ;// 通过 CAS 把 node 节点 push 到_cxq 列表中ObjectWaiter * nxt ;for (;;) {node._next = nxt = _cxq ;if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;// 再次 tryLockif (TryLock (Self) > 0) {assert (_succ != Self , "invariant") ;assert (_owner == Self , "invariant") ;assert (_Responsible != Self , "invariant") ;return ;}}for (;;) {// 本段代码的主要思想和 AQS 中相似可以类比来看// 再次尝试if (TryLock (Self) > 0) break ;assert (_owner != Self, "invariant") ;if ((SyncFlags & 2) && _Responsible == NULL) {Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;}// 满足条件则 park selfif (_Responsible == Self || (SyncFlags & 1)) {TEVENT (Inflated enter - park TIMED) ;Self->_ParkEvent->park ((jlong) RecheckInterval) ;// Increase the RecheckInterval, but clamp the value.RecheckInterval *= 8 ;if (RecheckInterval > 1000) RecheckInterval = 1000 ;} else {TEVENT (Inflated enter - park UNTIMED) ;// 通过 park 将当前线程挂起,等待被唤醒Self->_ParkEvent->park() ;}if (TryLock(Self) > 0) break ;// 再次尝试自旋if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;}return ;}
monitor 释放
当某个持有锁的线程执行完同步代码块时,会释放锁并 unpark 后续线程(由于篇幅只保留重要代码)。
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {Thread * Self = THREAD ;if (_recursions != 0) {_recursions--; // this is simple recursive enterTEVENT (Inflated exit - recursive) ;return ;}ObjectWaiter * w = NULL ;int QMode = Knob_QMode ;// 直接绕过 EntryList 队列,从 cxq 队列中获取线程用于竞争锁if (QMode == 2 && _cxq != NULL) {w = _cxq ;assert (w != NULL, "invariant") ;assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;ExitEpilog (Self, w) ;return ;}// cxq 队列插入 EntryList 尾部if (QMode == 3 && _cxq != NULL) {w = _cxq ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;if (u == w) break ;w = u ;}ObjectWaiter * q = NULL ;ObjectWaiter * p ;for (p = w ; p != NULL ; p = p->_next) {guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}ObjectWaiter * Tail ;for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;if (Tail == NULL) {_EntryList = w ;} else {Tail->_next = w ;w->_prev = Tail ;}}// cxq 队列插入到_EntryList 头部if (QMode == 4 && _cxq != NULL) {// 把 cxq 队列放入 EntryList// 此策略确保最近运行的线程位于 EntryList 的头部w = _cxq ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;if (u == w) break ;w = u ;}assert (w != NULL , "invariant") ;ObjectWaiter * q = NULL ;ObjectWaiter * p ;for (p = w ; p != NULL ; p = p->_next) {guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}if (_EntryList != NULL) {q->_next = _EntryList ;_EntryList->_prev = q ;}_EntryList = w ;}w = _EntryList ;if (w != NULL) {assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;ExitEpilog (Self, w) ;return ;}w = _cxq ;if (w == NULL) continue ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;if (u == w) break ;w = u ;}if (QMode == 1) {// QMode == 1 : 把 cxq 倾倒入 EntryList 逆序ObjectWaiter * s = NULL ;ObjectWaiter * t = w ;ObjectWaiter * u = NULL ;while (t != NULL) {guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;t->TState = ObjectWaiter::TS_ENTER ;u = t->_next ;t->_prev = u ;t->_next = s ;s = t;t = u ;}_EntryList = s ;assert (s != NULL, "invariant") ;} else {// QMode == 0 or QMode == 2_EntryList = w ;ObjectWaiter * q = NULL ;ObjectWaiter * p ;// 将单向链表构造成双向环形链表;for (p = w ; p != NULL ; p = p->_next) {guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}}if (_succ != NULL) continue;w = _EntryList ;if (w != NULL) {guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;ExitEpilog (Self, w) ;return ;}}}
notify 唤醒
notify 或者 notifyAll 方法可以唤醒同一个锁监视器下调用 wait 挂起的线程,具体实现如下
void ObjectMonitor::notify(TRAPS) {CHECK_OWNER();if (_WaitSet == NULL) {TEVENT (Empty - Notify);return;}DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);int Policy = Knob_MoveNotifyee;Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");ObjectWaiter *iterator = DequeueWaiter();if (iterator != NULL) {// 省略一些代码// 头插 EntryListif (Policy == 0) {if (List == NULL) {iterator->_next = iterator->_prev = NULL;_EntryList = iterator;} else {List->_prev = iterator;iterator->_next = List;iterator->_prev = NULL;_EntryList = iterator;}} else if (Policy == 1) { // 尾插 EntryListif (List == NULL) {iterator->_next = iterator->_prev = NULL;_EntryList = iterator;} else {ObjectWaiter *Tail;for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);assert (Tail != NULL && Tail->_next == NULL, "invariant");Tail->_next = iterator;iterator->_prev = Tail;iterator->_next = NULL;}} else if (Policy == 2) { // 头插 cxq// prepend to cxqif (List == NULL) {iterator->_next = iterator->_prev = NULL;_EntryList = iterator;} else {iterator->TState = ObjectWaiter::TS_CXQ;for (;;) {ObjectWaiter *Front = _cxq;iterator->_next = Front;if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {break;}}}} else if (Policy == 3) { // 尾插 cxqiterator->TState = ObjectWaiter::TS_CXQ;for (;;) {ObjectWaiter *Tail;Tail = _cxq;if (Tail == NULL) {iterator->_next = NULL;if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {break;}} else {while (Tail->_next != NULL) Tail = Tail->_next;Tail->_next = iterator;iterator->_prev = Tail;iterator->_next = NULL;break;}}} else {ParkEvent *ev = iterator->_event;iterator->TState = ObjectWaiter::TS_RUN;OrderAccess::fence();ev->unpark();}if (Policy < 4) {iterator->wait_reenter_begin(this);}}// 自旋释放Thread::SpinRelease(&_WaitSetLock);if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {ObjectMonitor::_sync_Notifications->inc();}}
偏向锁:通过对比 Mark Word thread id 解决加锁问题。
轻量级锁:是通过用 CAS 操作 Mark Word 和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。
重量级锁:是将除了拥有锁的线程以外的线程都阻塞。
相关文章:

锁升级之Synchronized
Synchronized JVM系统锁一个对象里如果有多个synchronized方法,同一时刻,只要有一个线程去调用其中的一个synchronized方法,其他线程只能等待!锁的是当前对象,对象被锁定后,其他线程都不能访问当前对象的其…...

基于nodejs+vue疫情网课管理系统
疫情网课也都将通过计算机进行整体智能化操作,对于疫情网课管理系统所牵扯的管理及数据保存都是非常多的,例如管理员:首页、个人中心、学生管理、教师管理、班级管理、课程分类管理、课程表管理、课程信息管理、作业信息管理、请假信息管理、上课签到管理、论坛交流…...

Zabbix 构建监控告警平台(三)
Zabbix User parametersZabbix Trigger1.Zabbix User parameters 1.1即自定义KEY 注意:mysql安装在被监测主机 [rootlocalhost ~]# yum -y install mariadb-server mariadb [rootlocalhost ~]# systemctl start mariadb [rootlocalhost ~]# mysqladmin -uroot statu…...
Linux系统之dool命令行工具的基本使用
Linux系统之dool命令行工具的基本使用一、dool命令行工具介绍二、本地系统环境检查1.检查系统版本2.检查系统内核版本三、下载dool软件包1.创建下载目录2.下载dool四、安装dool1.安装python32.安装dool五、dool的命令帮助六、dool的基本使用1.直接使用dool监控系统2.监控cpu和网…...

LeetCode-2335-装满杯子需要的最短总时长
1、堆 我们可以维护一个堆,首先我们将数组中不为0的数全部加入堆中,而后进行循环。当堆不为空时,我们将堆顶元素出堆并减一,而后观察是否还能继续出堆,若能则出堆,否则跳过,最后我们将处理后的…...
npm ERR! code ELIFECYCLE解决方案,npm犯错!myweb@1.0.0构建脚本失败。
1.问题npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! myweb1.0.0 build: webpack --config config/webpack.config.jsnpm ERR! Exit status 1npm ERR!npm ERR! Failed at the myweb1.0.0 build script.npm犯错!代码ELIFECYCLEnpm犯错!errno 1npm犯错!myweb1.0.0 build: we…...

最小二乘支持向量机”在学习偏微分方程 (PDE) 解方面的应用(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 本代码说明了“最小二乘支持向量机”在学习偏微分方程 (PDE) 解方面的应用。提供了一个示例,…...

ISYSTEM调试实践8-winIDEA Analyzer功能1
前面几篇介绍了ISYSTEM的基本调试界面和功能,相比我之前用过的IDE,除了几种断点方式和脚本功能以外,应该都是比较简单,稍微操作一下就可以直接上手,后续我将介绍winIDEA的Analyzer 功能。 1 Analyzer简介 iSYSTEM An…...

每日学术速递2.11
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.IR、cs.MM 1.A Comprehensive Survey on Multimodal Recommender Systems: Taxonomy, Evaluation, and Future Directions 标题:关于多模态推荐系统的综合调查:分…...

宝塔搭建实战php开源likeadmin通用管理admin端vue3源码(二)
大家好啊,我是测评君,欢迎来到web测评。 上一期给大家分享了server端的部署方式,今天来给大家分享admin端在本地搭建,与打包发布到宝塔的方法。感兴趣的朋友可以自行下载学习。 技术架构 vscode node16 vue3 elementPlus vit…...

网络基础-虚拟化工具-网桥
系列文章目录 本系列文章主要是回顾和学习工作中常用的网络基础命令,在此记录以便于回顾。 该篇文章主要是讲解虚拟化的工具网桥相关的概念和常用命令 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录系…...
剑指 Offer 14- II. 剪绳子 II
剑指 Offer 14- II. 剪绳子 II 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少&a…...
English Learning - Day55 作业打卡 2023.2.9 周四
English Learning - Day55 作业打卡 2023.2.9 周四引言1. Jim 在看电视的时候他的老婆正在做饭。2. 他刚睡着电话就响了。3. 我正在想事情,这时忽然有人从后面抓我胳膊。4. 我们总是边吃火锅边唱歌。5. 他一听说出了事故,马上就来了现场。6. He entered …...

pixhawk2.4.8-地面站配置-APM固件
文章目录一、硬件准备二、软件准备1 已实飞测试2 MP地面站 任意版本下载:3 APM固件 任意版本下载:三、飞控校准1 刷固件2 机架选择3 加速度计校准4 指南针校准5 遥控器校准6 飞行模式7 紧急断电&无头模式8 基础参数设置9 电流计校准10 电调校准11 起…...

golang 通道类型
文章目录一、什么是通道类型二、通道产生的原因三、声明channel四、创建channel五、channel相关操作1、发送值2、接收值3、关闭通道3.1 注意3.2 特点四、通道类型1、无缓冲通道2、有缓冲通道五、单向通道一、什么是通道类型 Go 语言中的通道(channel)是一…...
并发、并行、吞吐量、延迟、响应时间 含义理解
并发、并行、吞吐量、延迟、响应时间 知识点了解 1. 响应时间(RT) 理解:响应时间是指系统对请求作出响应的时间。例如一个正在运行的服务,服务内程序接受到参数请求开始,到程序计算完,并将结果返回出去结束,这段时间…...

HTTP 和 HTTPS 的区别
文章目录前言一、HTTP 与 HTTPS 的基本概念HTTPHTTPS二、HTTP 和 HTTPS协议的区别前言 浏览网站时,我们会发现网址有两种格式,一种以http://开头,一种https://开头。好像这两种格式差别不大,只多了一个s,实际上他们有…...

微搭低代码从入门到精通07-基础布局组件
低码开发不同于传统开发,传统开发我们通常需要编写前端代码和后端代码。前端代码由HTML、CSS和JavaScript组成,后端代码我们通常要用后端语言比如Java来编写接口。 低码开发的特点是可视化开发,在编辑器中通过组件的拖拽来完成页面的编制。如…...

Docker镜像的创建
Docker镜像Docker镜像Docker 镜像是一个特殊的文件系统提供容器运行时所需的程序、库、资源、配置等文件包含一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)镜像不包含任何动态数据,其内容在构建之后也不会被改变。Docker镜像的…...

电子技术——MOS差分输入对
电子技术——MOS差分输入对 差分输入系统因其极高的共模抑制能力,差分输入几乎是是构建所有通用模拟IC的基本前级输入,也是现代信号传输理论的基础。本节我们讲解MOS差分输入对。 MOS差分输入对 下图展示了MOS差分输入对的基本原理图: 一个…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...

中科院1区顶刊|IF14+:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点
中科院1区顶刊|IF14:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点 当下,免疫与代谢性疾病的关联研究已成为生命科学领域的前沿热点。随着研究的深入,我们愈发清晰地认识到免疫系统与代谢系统之间存在着极为复…...