ReentrantLock 原理
(一)、非公平锁实现原理
1、加锁解锁流程
先从构造器开始看,默认为非公平锁实现
public ReentrantLock() {sync = new NonfairSync();
}
NonfairSync 继承自 AQS
没有竞争时
加锁流程
- 构造器构造,默认构造非公平锁
- (无竞争,第一个线程尝试加锁时)加锁,luck(),
final void lock() {// 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 如果尝试失败,进入 ㈠acquire(1); }
首先尝试将锁的state改为1,如果修改成功,则将拥有锁的线程修改位为当前线程
- 当第一个竞争线程出现时,竞争线程尝试加锁,无法将state由0改为1,竞争线程进入方法acquire(1);
// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处 public final void acquire(int arg) {// ㈡ tryAcquireif (!tryAcquire(arg) &&// 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {selfInterrupt();} }
- 线程进入tryAcquire(arg)方法,再次尝试加锁,如果成功 !(tryAcquire(arg)) = false,退出流程,加锁成功
- 再次加锁失败!(tryAcquire(arg)) = true,进入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
- 先执行addWaiter(Node.EXCLUSIVE)方法,该方法是构造 Node 队列,在第一个竞争线程执行该方法时,除了创造关联本线程的节点,还会创造一个哑元节点(该节点就是列表的head节点,NonfairSync中的head也指向该节点),默认初始状态都为0,形成双向列表,返回值时关联竞争线程的那个Node节点
- 执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,
// AQS 继承过来的方法, 方便阅读, 放在此处 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (; ; ) {final Node p = node.predecessor();// 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取if (p == head && tryAcquire(arg)) {// 获取成功, 设置自己(当前线程对应的 node)为 headsetHead(node);// 上一个节点 help GCp.next = null;failed = false;// 返回中断标记 falsereturn interrupted;}if (// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p, node) &&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()) {interrupted = true;}}} finally {if (failed)cancelAcquire(node);} }
-
进入到for(;;)循环,找出当前节点的前驱节点定义为p,此时p就是哑元节点,此时 p == head,再次尝试获取锁(如果当前节点是排在第二位的节点,就可以尝试再次加锁),如果尝试加锁成功
-
尝试加锁失败,执行
if(// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p,node)&&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()){interrupted=true; }
-
执行shouldParkAfterFailedAcquire(p,node)方法
// AQS 继承过来的方法, 方便阅读, 放在此处 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取上一个节点的状态int ws = pred.waitStatus;if (ws == Node.SIGNAL) { //Node.SIGNAL = -1// 上一个节点都在阻塞, 那么自己也阻塞好了return true;}// > 0 表示取消状态if (ws > 0) {// 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 这次还没有阻塞// 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false; }
-
由于pred(p)的状态=0,所以进入compareAndSetWaitStatus(pred, ws, Node.SIGNAL),该方法时将pred(p)的状态改为-1,结束方法,返回false
-
回到之前的代码,进行下一次循环,再次执行if (p == head && tryAcquire(arg)),再次尝试加锁,如果成功,...... ,失败,进入
if(// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p,node)&&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()){interrupted=true; }
// AQS 继承过来的方法, 方便阅读, 放在此处 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (; ; ) {final Node p = node.predecessor();// 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取if (p == head && tryAcquire(arg)) {// 获取成功, 设置自己(当前线程对应的 node)为 headsetHead(node);// 上一个节点 help GCp.next = null;failed = false;// 返回中断标记 falsereturn interrupted;}if (// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p, node) &&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()) {interrupted = true;}}} finally {if (failed)cancelAcquire(node);} }
-
再次进入shouldParkAfterFailedAcquire(p,node),此时prep(p) = -1,返回true
// AQS 继承过来的方法, 方便阅读, 放在此处 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取上一个节点的状态int ws = pred.waitStatus;if (ws == Node.SIGNAL) { //Node.SIGNAL = -1// 上一个节点都在阻塞, 那么自己也阻塞好了return true;}// > 0 表示取消状态if (ws > 0) {// 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 这次还没有阻塞// 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false; }
-
进入parkAndCheckInterrupt()方法,当前线程进入阻塞状态
// 阻塞当前线程 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted(); }
-
多个线程竞争失败后,
-
此时,Thread-0执行完成,释放锁,调用ReentrantLock中的
public void unlock() {sync.release(1); }
-
进入sync.release(1)方法,
public final boolean release(int arg) {if (tryRelease(arg)) {AbstractQueuedSynchronizer.Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false; }
在tryRelease(arg)方法中,设置 exclusiveOwnerThread 为 null,state = 0,返回true(返回false 的情况下面再说)
-
执行到
AbstractQueuedSynchronizer.Node h = head; if (h != null && h.waitStatus != 0)unparkSuccessor(h);
-
此时h = head 不等于null,且h的状态!=0 (等于-1),进入unparkSuccessor(h)方法,唤醒后继节点,此时node (h) 的状态=-1,h的后继节(s)点 != null,执行 if (s != null) LockSupport.unpark(s.thread); 唤醒s线程,s线程开始竞争锁
private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling. It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node. But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/AbstractQueuedSynchronizer.Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread); }
-
s(Thread-1)线程回到
// 阻塞当前线程 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted(); }
继续执行
-
返回到
// AQS 继承过来的方法, 方便阅读, 放在此处 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (; ; ) {final Node p = node.predecessor();// 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取if (p == head && tryAcquire(arg)) {// 获取成功, 设置自己(当前线程对应的 node)为 headsetHead(node);// 上一个节点 help GCp.next = null;failed = false;// 返回中断标记 falsereturn interrupted;}if (// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p, node) &&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()) {interrupted = true;}}} finally {if (failed)cancelAcquire(node);} }
继续进行for循环,此时if (p == head && tryAcquire(arg)) ,在次尝试加锁,此时如果加锁成功,执行以下代码
setHead(node); // 上一个节点 help GC p.next = null; failed = false; // 返回中断标记 false return interrupted;
将关联s线程(刚才关联Thread-1线程的节点)的节点设置为头节点(删除之前的头节点,将此节点关联的线程改为null)
-
如果刚才thread-1线程唤醒后,新出现了一个线程与之竞争,且thread-1线程竞争失败,在次进入parkAndCheckInterrupt(),进入阻塞状态
2、可重入原理
ReentrantLock的非公平获取锁的源码
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
static final class NonfairSync extends Sync {// ...// 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()) {// state++int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// Sync 继承过来的方法, 方便阅读, 放在此处protected final boolean tryRelease(int releases) {// state--int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 支持锁重入, 只有 state 减为 0, 才释放成功if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}
}
- 当一个线程第一次获得锁时,进入代码
if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;} }
把锁的state设置为1,把拥有锁的线程设置为当前线程,返回true
- 当一个线程多次获得锁时(锁重入),进入代码
else if (current == getExclusiveOwnerThread()) {// state++int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true; }
让state++,返回true
- 当锁重入后释放锁时,进入
protected final boolean tryRelease(int releases) {// state--int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 支持锁重入, 只有 state 减为 0, 才释放成功if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}
让state--,如果state != 0 返回false,如果=0,设置当前拥有锁的线程为null,返回true
3、可打断原理
(1)、不可打断(默认)
在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
// Sync 继承自 AQS
static final class NonfairSync extends Sync {// ...private final boolean parkAndCheckInterrupt() {// 如果打断标记已经是 true, 则 park 会失效LockSupport.park(this);// interrupted 会清除打断标记return Thread.interrupted();}final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (; ; ) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null;failed = false;// 还是需要获得锁后, 才能返回打断状态return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {// 如果是因为 interrupt 被唤醒, 返回打断状态为 trueinterrupted = true;}}} finally {if (failed)cancelAcquire(node);}}public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {// 如果打断状态为 trueselfInterrupt();}}static void selfInterrupt() {// 重新产生一次中断Thread.currentThread().interrupt();}
}
- 被打断后,进入方法,return true,但是Thread.interrupted()会重置打断标记为false
// 阻塞当前线程 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted(); }
-
回退到
if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {// 如果是因为 interrupt 被唤醒, 返回打断状态为 trueinterrupted = true; }
置interrupted = true
-
接着循环,接着进入到
// 阻塞当前线程 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted(); }
进入阻塞状态,但再次被唤醒之后器其返回值仍然时true
-
直到该线程获得所之后,执行
if (p == head && tryAcquire(arg)) {setHead(node);p.next = null;failed = false;// 还是需要获得锁后, 才能返回打断状态return interrupted; }
返回true
-
回退到
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {// 如果打断状态为 trueselfInterrupt();}}static void selfInterrupt() {// 重新产生一次中断Thread.currentThread().interrupt();}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)返回值时true,执行selfInterrupted(),打断当前进程
在不可打断模式下,只要任务在AQS队列中,就不能打断
(2)、可打断
// ㈠ 可打断的获取锁流程
private void doAcquireInterruptibly(int arg) throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {// 在 park 过程中如果被 interrupt 会进入此// 这时候抛出异常, 而不会再次进入 for (;;)throw new InterruptedException();}}} finally {if (failed)cancelAcquire(node);}
}
打断后直接抛出异常
(二)、公平锁实现原理
static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}// AQS 继承过来的方法, 方便阅读, 放在此处public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {selfInterrupt();}}// 与非公平锁主要区别在于 tryAcquire 方法的实现protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}} else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处public final boolean hasQueuedPredecessors() {Node t = tail;Node h = head;Node s;// h != t 时表示队列中有 Nodereturn h != t &&(// (s = h.next) == null 表示队列中没有老二(s = h.next) == null || // 或者队列中老二线程不是此线程s.thread != Thread.currentThread());}
}
在获取锁时,要限先执行方法hasQueuedPredecessors(),该方法当队列中
没有第二位(没有老二是因为这时候另一个线程在初始化这个队列,刚好head被创建出来了但是没有设置next)
或者
第二位节点不是当前节点时,返回true,取反为false,无法获取锁,返回false
(三)、条件变量实现原理
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
1、await流程
// 等待 - 直到被唤醒或打断
public final void await() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();}// 添加一个 Node 至等待队列, 见 ㈠Node node = addConditionWaiter();// 释放节点持有的锁int savedState = fullyRelease(node);int interruptMode = 0;// 如果该节点还没有转移至 AQS 队列, 阻塞while (!isOnSyncQueue(node)) {// park 阻塞LockSupport.park(this); // 如果被打断, 退出等待队列if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}// 退出等待队列后, 还需要获得 AQS 队列的锁if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;// 所有已取消的 Node 从队列链表删除, 见 ㈡if (node.nextWaiter != null)unlinkCancelledWaiters();// 应用打断模式, 见 ㈤if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}
- 先进入addConditionWaiter()方法,创建一个顶的node节点,将其挂到ConditionObject中,将其状态置为-2,返回这个节点
// 添加一个 Node 至等待队列 private Node addConditionWaiter() {Node t = lastWaiter;// 所有已取消的 Node 从队列链表删除, 见 ㈡if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}// 创建一个关联当前线程的新 Node, 添加至队列尾部Node node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node; }
- 执行int savedState = fullyRelease(node),
final int fullyRelease(AbstractQueuedSynchronizer.Node node) {boolean failed = true;try {int savedState = getState();if (release(savedState)) {failed = false;return savedState;} else {throw new IllegalMonitorStateException();}} finally {if (failed)node.waitStatus = AbstractQueuedSynchronizer.Node.CANCELLED;} }
进入release(savedState)
public final boolean release(int arg) {if (tryRelease(arg)) {AbstractQueuedSynchronizer.Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false; }
进入tryRelease(arg)中,将state置为0,将拥有锁的线程设置为null
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free; }
返回
public final boolean release(int arg) {if (tryRelease(arg)) {AbstractQueuedSynchronizer.Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false; }
唤醒head的后继节点
- 返回到await,进入while循环,阻塞当前线程
// 等待 - 直到被唤醒或打断 public final void await() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();}// 添加一个 Node 至等待队列, 见 ㈠Node node = addConditionWaiter();// 释放节点持有的锁int savedState = fullyRelease(node);int interruptMode = 0;// 如果该节点还没有转移至 AQS 队列, 阻塞while (!isOnSyncQueue(node)) {// park 阻塞LockSupport.park(this); // 如果被打断, 退出等待队列if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}// 退出等待队列后, 还需要获得 AQS 队列的锁if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;// 所有已取消的 Node 从队列链表删除, 见 ㈡if (node.nextWaiter != null)unlinkCancelledWaiters();// 应用打断模式, 见 ㈤if (interruptMode != 0)reportInterruptAfterWait(interruptMode); }
2、signal流程
让Thread-1线程唤醒Thread-0线程
public final void signal() {if (!isHeldExclusively()) //判断当前线程是否是拥有锁的线程throw new IllegalMonitorStateException();AbstractQueuedSynchronizer.Node first = firstWaiter; //获取队首的节点if (first != null)doSignal(first);
}
- 执行doSignal(first)方法
private void doSignal(AbstractQueuedSynchronizer.Node first) {do {if ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;first.nextWaiter = null;} while (!transferForSignal(first) &&(first = firstWaiter) != null); }
将当前的节点从ConditionObject的队列中断开
执行transferForSignal(first)方法
final boolean transferForSignal(AbstractQueuedSynchronizer.Node node) {if (!compareAndSetWaitStatus(node, AbstractQueuedSynchronizer.Node.CONDITION, 0))return false;AbstractQueuedSynchronizer.Node p = enq(node);int ws = p.waitStatus;if (ws > 0 || !compareAndSetWaitStatus(p, ws, AbstractQueuedSynchronizer.Node.SIGNAL))LockSupport.unpark(node.thread);return true; }
先将当前节点的状态设置为0,进入enq(node)方法,将node挂在到阻塞队列末尾,返回node的前驱节点(Thread-3)记为p,将p的状态设置为-1,然后返回true
相关文章:

ReentrantLock 原理
(一)、非公平锁实现原理 1、加锁解锁流程 先从构造器开始看,默认为非公平锁实现 public ReentrantLock() {sync new NonfairSync(); } NonfairSync 继承自 AQS 没有竞争时 加锁流程 构造器构造,默认构造非公平锁(无竞争,第一个线程尝试…...

星云小窝项目1.0——项目介绍(一)
星云小窝项目1.0——项目介绍(一) 文章目录 前言1. 介绍页面2. 首页2.1. 游客模式2.2. 注册用户后 3. 星云笔记3.1. 星云笔记首页3.2. 星云笔记 个人中心3.2. 星云笔记 系统管理3.3. 星云笔记 文章展示3.3. 星云笔记 新建文章 4. 数据中心5. 交流评论6. …...
VR虚拟仿真在线模拟旅游专业情景
旅游专业运用VR虚拟仿真教学的教学优势主要包括: 1. 增强教学效果:VR技术能够提供身临其境的体验,使学生更容易理解和记住某些概念和理论。例如,学生可以通过虚拟旅行来了解某个国家的文化、历史和景点,这将比传统的课…...

ROS 2边学边练(3)-- 何为节点(nodes)
在接触节点这个概念之前,我们先来看看下面这张动态图,更方便我们理解一些概念和交互过程。 (相信大家的英文基础哈) 概念 如上图所示,这里面其实涉及到了三个概念(功能),分别是节点…...

MySQL的主从复制和读写分离
目录 相关知识: 1. 主从复制和读写分离 2. mysql 支持的复制类型 对比: 一. 主从复制 1. 原理和工作过程 工作过程: 注意: 中继日志(Relay Log): 2. 一些理解问题 2.1 为什么要复制 …...
C# 多态 派生类 abstract virtual new
静态多态函数重载运算符重载 动态多态abstract 和 virtual的区别定义与用途:成员实现:继承与重写:与接口的区别: 使用抽象类的好处主要体现在以下几个方面:代码重用:设计灵活性:接口定义&#x…...

【爬虫基础】第10讲 urlerror的使用及捕获异常
URLError是Python中的一个异常类,用于处理与URL相关的错误。它是urllib.error模块中的一个类。 URLError通常在以下情况下被引发: 网络连接问题:例如无法连接到服务器、超时等。URL不正确:例如无效的URL、无法解析主机名等。服务…...
绍兴越城中墙建材蒸压加气混凝土砌块使用注意事项可送塔山府山北海蕺山城南稽山迪荡灵芝东湖皋埠马山斗门鉴湖东浦孙端陶堰富盛
绍兴越城中墙建材蒸压加气混凝土砌块使用注意事项可送塔山府山北海蕺山城南稽山迪荡灵芝东湖皋埠马山斗门鉴湖东浦孙端陶堰富盛 使用蒸压加气混凝土砌块时需要注意以下事项: 选择符合国家标准的产品:选购时应查看产品质量证明书,确保产品符合…...

吴渔夫:AI技术引领游戏产业革命,小团队有大作为
AI技术的突飞猛进,游戏产业正在经历一场前所未有的变革。中国网游先锋,火石控股创始人吴渔夫,近日在接受第一财经日报的采访,对AI在游戏制作中的应用和未来趋势有着深刻的见解。 吴渔夫指出,AI技术的引入极大地降低了游…...
深入探索C++对象模型(二)
类对象占用的空间 #include "pch.h" #include <iostream> using namespace std;class A {public: };//类对象所占用的空间 int main() {//std::cout << "Hello World!\n"; A obja;int ilen = sizeof(obja); cout << ilen << endl…...

【javaWeb 第三篇】Vue快速入门
VUE vue是一套前端框架,免除原生的js的DOM操作,简化书写 基于MVVM(model-view-viewmodel)思想,实现数据的双向绑定,将编程的关注放在数据上。 什么是框架: 框架相当于一个半成品,是一…...
非root用户安装git lfs(git大文件)命令记录
背景 最近在看LLAMA2的模型,想直接从Huggingface下载模型到本地,但是却发现服务器上没有安装git lfs命令。查询了一些资料完成了非root用户安装git lfs命令的操作,特此记录。 Git LFS下载与解压 下载 Git LFS 二进制文件 访问 Git LFS 发布…...
PTA 道路管制
乌拉乌拉国有n个城市和m条道路,城市编号为1∼n。由于乌拉乌拉国每一个城市都在创城(创建文明城市),因此,城市之间的道路通行施行道路交通管制: 已知从城市ui到城市vi的道路,需要时间ti。…...

自媒体用ChatGPT批量洗稿软件V5.9环境配置/软件设置教程【汇总】
大家好,我是淘小白~ 首先,感谢大家的支持~~ ChatGPT采集洗稿软件V5.9版本更新,此次版本更新修改增加了一些内容: 1、自定义多条指令,软件自动判断指令条数,进行输入 2、增加谷歌浏览多账号轮询…...
【WPF应用7】 基本控件-Grid 布局的详解与示例
引言 WPF(Windows Presentation Foundation)是.NET框架的一部分,它提供了一个用于创建桌面应用程序用户界面的框架。在WPF中,Grid布局是一个非常强大的布局工具,它允许开发者创建复杂的、响应迅速的用户界面布局。Grid…...
flink-connector-redis支持select查询
EN 1 项目介绍 基于bahir-flink二次开发,相对bahir调整的内容有: 1.使用Lettuce替换Jedis,同步读写改为异步读写,大幅度提升了性能 2.增加了Table/SQL API,增加select/维表join查询支持 3.增加关联查询缓存(支持增量与全量) 4…...

[密码学] 密码学基础
目录 一 为什么要加密? 二 常见的密码算法 三 密钥 四 密码学常识 五 密码信息威胁 六 凯撒密码 一 为什么要加密? 在互联网的通信中,数据是通过很多计算机或者通信设备相互转发,才能够到达目的地,所以在这个转发的过程中,如果通信包…...

上海:6月1日起取消企业复工复产白名单制
财经新闻5月29日消息:上海市人民政府关于印发《上海市加快经济恢复振兴行动计划》的通知。 《方案》包括千方百计缓解各类市场主体困难,全面有序推进复工复产和市场复工复产,多措并举稳外资稳外贸,大力促进消费加速复苏࿰…...

SpringBoot扩展篇:循环依赖源码链路
SpringBoot扩展篇:循环依赖源码链路 1. 相关文章2. 一个简单的Demo3. 流程图3.1 BeanDefinition的注册3.2 开始创建Bean3.3 从三级缓存获取Bean3.4 创建Bean3.5 实例化Bean3.6 添加三级缓存3.7 属性初始化3.8 B的创建过程3.9 最终流程 1. 相关文章 SpringBoot 源码…...

服务消费微服务
文章目录 1.示意图2.环境搭建1.创建会员消费微服务模块2.删除不必要的两个文件3.检查父子模块的pom.xml文件1.子模块2.父模块 4.pom.xml 添加依赖(刷新)5.application.yml 配置监听端口和服务名6.com/sun/springcloud/MemberConsumerApplication.java 创…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...