ReentrantLock 原理

(一)、非公平锁实现原理

1、加锁解锁流程

先从构造器开始看,默认为非公平锁实现

public ReentrantLock() {sync = new NonfairSync();
}

NonfairSync 继承自 AQS

没有竞争时

加锁流程

  1. 构造器构造,默认构造非公平锁
  2. (无竞争,第一个线程尝试加锁时)加锁,luck(),
    final void lock() {// 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 如果尝试失败,进入 ㈠acquire(1);
    }

    首先尝试将锁的state改为1,如果修改成功,则将拥有锁的线程修改位为当前线程

  3. 当第一个竞争线程出现时,竞争线程尝试加锁,无法将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();}
    }
  4. 线程进入tryAcquire(arg)方法,再次尝试加锁,如果成功 !(tryAcquire(arg)) = false,退出流程,加锁成功
  5. 再次加锁失败!(tryAcquire(arg)) = true,进入  acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
  6. 先执行addWaiter(Node.EXCLUSIVE)方法,该方法是构造 Node 队列,在第一个竞争线程执行该方法时,除了创造关联本线程的节点,还会创造一个哑元节点(该节点就是列表的head节点,NonfairSync中的head也指向该节点),默认初始状态都为0,形成双向列表,返回值时关联竞争线程的那个Node节点
  7. 执行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);}
    }
  8. 进入到for(;;)循环,找出当前节点的前驱节点定义为p,此时p就是哑元节点,此时                 p == head,再次尝试获取锁(如果当前节点是排在第二位的节点,就可以尝试再次加锁),如果尝试加锁成功

  9. 尝试加锁失败,执行

    if(// 判断是否应当 park, 进入 ㈦shouldParkAfterFailedAcquire(p,node)&&// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧parkAndCheckInterrupt()){interrupted=true;
    }
  10. 执行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;
    }
  11. 由于pred(p)的状态=0,所以进入compareAndSetWaitStatus(pred, ws, Node.SIGNAL),该方法时将pred(p)的状态改为-1,结束方法,返回false

  12. 回到之前的代码,进行下一次循环,再次执行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);}
    }
  13. 再次进入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;
    }
  14. 进入parkAndCheckInterrupt()方法,当前线程进入阻塞状态

    // 阻塞当前线程
    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
    }

  15. 多个线程竞争失败后,

  16. 此时,Thread-0执行完成,释放锁,调用ReentrantLock中的

    public void unlock() {sync.release(1);
    }
  17. 进入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 的情况下面再说)

  18. 执行到

    AbstractQueuedSynchronizer.Node h = head;
    if (h != null && h.waitStatus != 0)unparkSuccessor(h);
  19. 此时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);
    }
  20. s(Thread-1)线程回到

    // 阻塞当前线程
    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
    }

    继续执行

  21. 返回到

    // 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)

  22. 如果刚才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;}
}
  1. 当一个线程第一次获得锁时,进入代码
    if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}
    }

    把锁的state设置为1,把拥有锁的线程设置为当前线程,返回true

  2. 当一个线程多次获得锁时(锁重入),进入代码
    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

  3. 当锁重入后释放锁时,进入
        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();}
}
  1. 被打断后,进入方法,return true,但是Thread.interrupted()会重置打断标记为false
    // 阻塞当前线程
    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
    }
  2. 回退到

    if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {// 如果是因为 interrupt 被唤醒, 返回打断状态为 trueinterrupted = true;
    }

    置interrupted = true

  3. 接着循环,接着进入到

    // 阻塞当前线程
    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
    }

    进入阻塞状态,但再次被唤醒之后器其返回值仍然时true

  4. 直到该线程获得所之后,执行

    if (p == head && tryAcquire(arg)) {setHead(node);p.next = null;failed = false;// 还是需要获得锁后, 才能返回打断状态return interrupted;
    }

    返回true

  5. 回退到

        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);
}
  1. 先进入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;
    }
  2. 执行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的后继节点

  3. 返回到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);
}
  1. 执行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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023自适应霍夫曼编码High-performance RDHEI with adaptive Huffman code

RRBE 本文仅供自我学习使用,切勿转载和搬运,如有侵权,联系立删~ 方法总框架 首先由内容所有者生成原始图像像素点的标签映射; 然后数据隐藏者采用自适应霍夫曼编码将多个信息插入加密图像像素点;接收端进行数据提取和图像恢复。在数据提取之前,必须对标记的加密图像进行…

DC-7靶场

一.环境搭建 1.下载地址 靶机下载地址:https://download.vulnhub.com/dc/DC-7.zip 2.虚拟机配置 设置nat&#xff0c;打开靶机&#xff0c;遇到错误点重试和是 开启后如图所示即可 二.开始渗透 1.信息收集 首先找到靶机的ip地址 arp-scan -l 发现靶机的ip地址为192.168.…

分布式ID生成方案总结

分布式场景下&#xff0c;需要保证每一个服务拿到的id是唯一的。本文讨论、分析、总结了一些常见的分布式ID生成方案 结论&#xff1a;技术上没有银弹&#xff0c;每种分布式id都有自己的使用场景。uuid适用于业务比较简单&#xff0c;对性能没有太高追求等。 目前主流是 基于数…

某手信息流如何跳V,达到精准数据回传?

在数字营销的新纪元中&#xff0c;快手磁力引擎凭借其卓越的推广能力&#xff0c;已经吸引了无数品牌的目光。而为了更有效地将快手上的流量转化为企业微信的用户&#xff0c;数灵通正逐渐崭露头角&#xff0c;成为品牌方的得力助手。今天&#xff0c;我们就来探讨一下&#xf…

【问题处理】蓝鲸监控-数据断点解决

本文来自腾讯蓝鲸智云社区用户&#xff1a;fadewalk 在问答社区看到有小伙伴在落地蓝鲸的过程中出现监控平台的grafana面板数据断点问题&#xff0c;往往出现这种问题&#xff0c;都比较的头疼。 如果将CMDB&#xff08;配置管理数据库&#xff09;比作运维的基石&#xff0c;…

Soft Robotics:两栖环境下螃蟹仿生机器人的行走控制

传统水陆两栖机器人依靠轮胎或履带与表面的接触及摩擦产生推进力&#xff0c;这种对于表面接触的依赖性限制了现有水陆两栖机器人在低重力环境下&#xff08;如水中&#xff09;的机动性。利用生物自身的推进机制&#xff0c;人为激发生物运动行为&#xff0c;由活体生物与微机…

电阻的妙用:限流、分压、滤波,助力电路设计!

电阻可以降低电压&#xff0c;这是通过电阻的分压来实现的。事实上&#xff0c;利用电阻来降低电压只是电阻的多种功能之一。电路中的电阻与其他元件&#xff08;电容、电感&#xff09;结合用于限流、滤波等。&#xff08;本文素材来源&#xff1a;https://www.icdhs.com/news…

C++ :STL中vector扩容机制

vector是STL提供的动态数组&#xff0c;它会在内部空间不够用时动态的调整自身的大小&#xff0c;调整过程中会有大量的数据拷贝&#xff0c;为了减少数据拷贝的次数vector会在调整空间的时候尽量多申请一些空间&#xff0c;这些预留出的空间可以很大程度上减少拷贝的发生。 在…

适合新手小白的wordpress详细安装教程

1、下载程序 到wordpress官方网站下载wordpress程序&#xff0c;官方下载地址&#xff1a;Download | WordPress.org China 简体中文。 下载最新版的wordpress程序 https://cn.wordpress.org/latest-zh_CN.zip 2、上传程序 上传程序前先确认主机是否符合安装的环境要求&…

爬虫基础训练题

1.抓取imooc网站实战课程部分的课程名称&#xff08;所有课程大概7页&#xff0c;抓取1到5页&#xff09;&#xff0c;并把所有课程名称存储为txt文件第一页地址 2.设置一个请求头&#xff08;headers&#xff09;&#xff0c;这是一个字典&#xff0c;用于在HTTP请求中设置请…

职场人必备!效率翻倍的多微信号必备管理工具大揭秘

在职场中&#xff0c;高效率的工作方式是非常重要的。而为了提高工作效率&#xff0c;合理运用一些工作神器也是必不可少的。今天给大家分享一个多微信号管理工具——微信管理系统&#xff0c;它能够帮助职场人员管理多个微信号&#xff0c;让工作变得更加高效。 首先&#xf…

mamba的学习记录

最近新出了一种很火的架构mamba&#xff0c;听说吊打transformer&#xff0c;特此学习一下&#xff0c;总结一下学习的内容。 state-spaces/mamba (github.com)3个月8Kstar&#xff0c;确实有点受欢迎。 目录 1.先验 RNN​ LSTM ​2.mamba State Space Models​ Selecti…

React和Vue.js的有什么区别

在当今前端开发领域&#xff0c;React 和 Vue.js 作为两大热门的前端框架备受开发者关注。它们各自拥有独特的特点和优势&#xff0c;在实际项目中有着广泛的运用。本文将深入探讨 React 和 Vue.js 之间的区别&#xff0c;从组件化方式、数据绑定、模板语法以及生态系统和工具支…

案例 | 华院计算x第一财经:我和我的数智人唱双簧

创新关乎命运&#xff0c;科技引领未来。生成式人工智能(AIGC)给传媒行业发展带来严峻挑战的同时&#xff0c;也带来千载难逢的重大发展机遇。2024年政府工作报告中提出&#xff0c;要深化大数据、人工智能等研发应用&#xff0c;开展“人工智能”行动&#xff0c;打造具有国际…

俚语加密漫谈

俚语加密是一种古老而有效的通信方式&#xff0c;将特定词语或短语在群体内赋予特殊含义&#xff0c;从而隐藏真实信息。类似于方言&#xff0c;它在历史上的应用不可忽视。随着计算机时代的到来&#xff0c;现代密码学通过数学运算编织密语&#xff0c;使得加密变得更加高深莫…

【win10 win11添加右键】git bash

打开注册表编辑器。 按下Win键 R&#xff0c;然后输入”regedit”并按下回车键来打开注册表编辑器。计算机\HKEY_CLASSES_ROOT\Directory\Background\shell\git_bash\command2. 导航到注册表路径&#xff1a;依次展开”HKEY_CLASSES_ROOT\Directory\Background\shell”。右键…

【数据结构】 HashMap源码分析(常量+构造方法+方法)

文章目录 HashMap源码分析一、成员常量二、构造方法三、方法1.此时假定为进行了无参构造&#xff0c;没有分配内存2.当发生有参构造时&#xff0c;完成对容量的大小判断后&#xff0c;将容量大小&#xff0c;传进tableSizeFor方法中&#xff1a; HashMap源码分析 一、成员常量…

如何使用Docker轻松构建和管理应用程序(二)

上一篇文章介绍了 Docker 基本概念&#xff0c;其中镜像、容器和 Dockerfile 。我们使用 Dockerfile 定义镜像&#xff0c;依赖镜像来运行容器&#xff0c;因此 Dockerfile 是镜像和容器的关键&#xff0c;Dockerfile 可以非常容易的定义镜像内容&#xff0c;同时在我们后期的微…

JDK8中ArrayList扩容机制

前言 这是基于JDK8的源码分析&#xff0c;在JDK6之前以及JDK11之后细节均有变动&#xff01;&#xff01; 首先来看ArrayList的构造方法 public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Seriali…

LeetCode刷题---查询近30天活跃用户数

1.给出满足的条件&#xff0c;截止至2019-07-27的近30天 activity_date BETWEEN DATE_ADD(2019-07-27,INTERVAL -29 day) and 2019-07-27这里使用了Between and 函数和 Date_add函数 2.按照日期分组&#xff0c;统计活跃用户个数 select activity_date day,count(distinct(us…