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

并发编程 java锁机制

1、什么是锁,为什么需要锁?

并发环境下,会存在多个线程对同一个资源进行争抢的情况,假设线程A对资源正在进行修改,此时线程B又对同一资源进行了修改,就会导致数据不一致的问题。为了解决这个问题,很多编程语言引入了锁机制。

通过一种抽象的“锁”来对资源进行锁定,当一个线程持有“锁”时,其他线程必须等待 ------  在临界资源上对线程进行一种串行化处理。

2、悲观锁 VS 乐观锁

乐观锁和悲观锁是一种广义上的概念,体现了看待线程并发时的角度。

  • 悲观锁:认为自己在使用数据的时候一定会有别的线程修改数据,因此获取数据时会先加锁,确保不会被别的线程修改
  • 乐观锁:认为自己使用数据的时候不会有别的线程修改数据,所以不会去加锁,只是在更新数据的时候去判断有没有别的线程更新了这个数据,如果没有被更新,则自己执行相关操作,如果被其他线程更新了,则执行对应的操作(重试或者报错)
3、介绍一下悲观锁。

java中,悲观锁的实现是基于object的 ------ 也就是说,每个对象都拥有一把锁,这个锁存放在对象头中,记录了当前对象被哪个线程所占用(持有)。

3.1、对象结构,对象头结构

首先说一下对象的结构:

  • 对象头
  • 实例数据
  • 填充字节(为了满足java对象大小是8字节的整数倍,没有实际意义)

对象头结构:

  • mark word --- 见下图
  • class pointer  --- 是一个指针,指向当前对象类型所在方法区中的class信息

相比于实例数据,对象头其实是一种额外的开销,因此设计的很小(32bit或者64bit)

从上图可以看到,“锁”的信息就存放在mark word中,根据后两个标志位,锁的状态又可以分为 无锁,偏向锁,轻量级锁,重量级锁。---  java中启用对象锁的方式就是 synchronized 关键字。下面会详细介绍一下synchronized背后的原理。

3.2 synchronized关键字

在java中,synchronized关键字可以用来加锁,而synchronized被编译后会生成两个字节码指令:monitorenter 和 monitorexit, 这两个字节码指令底层是依赖于操作系统的 mutex lock 来实现的,这个时候,java线程实际就是操作系统线程的映射,每当唤醒或者挂起一个线程的时候,都需要切换到操作系统的内核态,这个操作是重量级的(换句话说就是比较耗费时间,甚至切换的时间就超出了线程执行任务的时间。)因此,使用synchronized其实是会对程序的性能产生影响。

 刚刚提到了monitor(又叫监视器、管程等),我们可以将它理解为只能容纳一个客人的饭店,客人就是想要获取对象锁的线程,一旦有一个线程进入了monitor,那么其他线程只能等待,只有当这个线程退出去,其他线程才有机会进入。

3.3 ,介绍一下对象锁的四种状态
1、无锁

顾名思义就是没有对系统资源进行锁定,1)比如某种资源不会出现在多线程环境下,或者即便在多线程环境下面也不会有竞争情况,那么就不需要加锁。2)虽然会被竞争,但是采用了其他机制来控制,而不是使用操作系统对资源锁定(CAS,后面会介绍)。

2、偏向锁

假如一个对象被加锁了,但是实际运行过程中,只有一个线程会获取这个对象锁(前提),最理想的方式是只在用户态就把锁交出去,而不是通过操作系统来切换线程状态。 ---- 如果对象能够认识这个唯一的线程,只要这个线程过来,就直接把锁交出去就行了。(对象偏爱这个线程,称为偏向锁)

偏向锁是怎么实现的呢? 回到mark word 中,当锁标志位为01的时候,判断倒数第三个bit, 如果是1,则表示当前对象锁的状态为偏向锁,此时去读mark word的前23bit,这记录的就是线程ID,可以通过线程id来判断这个线程是不是被偏爱的线程。

3、轻量级锁

某一个时间,情况发生了变化,不止一个线程想要获取这个锁了(两个线程),这个时候,偏向锁就会升级成为轻量级锁。

轻量级锁是如何实现的? --- Markword 后两位变成00的时候,就意味着当前对象锁的状态是轻量级锁了,此时不在用线程id了,前30位变成了指向虚拟机栈中锁记录的指针。

当一个线程想要获取某个对象锁,发现Markword 后两位是00,此时会在自己的虚拟机栈中开辟一块成为 lock record 的空间,这个lock record存放的是Markword 的副本以及一个owner指针

线程通过cas去尝试获取锁,一旦获得,就会复制这个对象的Markword到自身虚拟机栈的lock record中,并且将lock record中的owner指针指向该对象锁。同时,对象的mark word的前30位生成一个指针,指向持有改对象锁的线程虚拟机栈中的lock record,这样就完成了线程和对象锁的绑定,双方都知道各自的存在了

此时,获得了对象锁的线程,就可以执行对应的任务了,那么没有获得锁的线程应该怎么办呢?--- 没有获得锁的线程会自旋等待,自旋就是一种轮询,不断判断锁有没有被释放,如果释放了,就获取锁,如果没有释放,就下一轮循环。 --- 这种自旋区别于被操作系统挂起阻塞,因为如果对象很快被释放的话,自旋获取锁就可以在用户态解决,而不用切换到内核态,效率比较高。

但是自旋其实就是CPU在空转,长时间的自旋会浪费CPU资源,于是出现了一种叫做“适应性自旋”的优化。简单来说就是,自旋时间不在固定了,而是又上一次在同一个锁上的自旋时间以及锁状态来决定,比如在同一个锁上,当前自旋等待的线程刚刚成功获得过锁,但是此时锁被其他线程持有,虚拟机就会认为下次自旋获取锁的概率很大,进而运行更长时间的自旋。

4、重量级锁

一旦自旋等待的线程超过一个,即有三个及以上的线程想获取同一个锁,这个时候就会升级成为重量级锁,这个时候就是通过monitor来对线程进行控制了,一旦进入了这个状态,就会调用内核空间,产生极大的开销。

上述描述了对象锁的四种状态,需要注意的是,锁只能升级,不能降级

4、介绍一下乐观锁

在讲述悲观锁的时候,提到了“无锁”这个概念,其中有一种是共享资源会出现被竞争的情况,但是不是适用操作系统同步原语进行保护,而是使用CAS这种方式进行线程同步,尽量将获取锁释放锁的操作在用户空间内完成,减少用户态和内核态之间的切换次数,提升程序的性能。 --------- 可以看到,其实乐观锁并不是锁,而是一种无锁的实现。

CAS就是实现乐观锁的一种经典巧妙的算法。 compare and swap,简单的翻译为,比较然后交换。

4.1, 如何理解CAS?

举例:比如说厕所的坑位,里面没人的时候你才可以进去,有人就只能在外面等着,设定开门状态是0,关门状态是1, 某一时间,两个人都想上厕所,A先冲了过去,并把门关上,这个时候B才过来,但是发现门已经关了,但是B也不能放弃啊,就不断回来看看门打开了没。

上述例子中,AB两个人就是代表线程,坑位就是共享资源,这样就应该比较容易理解CAS了,当一个共享资源状态值为0的一瞬间,AB线程读到了,此时两个线程都认为当前这个共享资源没有被占用,于是他们会各自生成两个值:

  • old value,代表之前读到的共享资源对象的状态值 -- 上述例子中都为0
  • new value,代表想要将共享资源对象状态值更新的值  -- 上述例子中都为1

此时AB线程都去争抢修改对象的状态值,然后占用对象。假设A运气比较好, A将old value 和 资源对象的状态值进行compare 比较,发现是一致的,于是将对象的状态值 swap 为new value; B落后一步,compare的时候发现自己的old value 和对象的状态值不一样,只能放弃swap操作(一般是自旋操作,同时配置自旋次数防止死循环,默认值10 )。

上述是一个CAS函数,但是其实是有问题的,因为这个函数本身没有任何同步措施,还是存在不安全的问题,因此,想要通过 CAS实现乐观锁,有一个必须的前提:CAS操作本身必须是原子性的

那么CAS是如何实现原子性的呢? --- 不同架构的CPU都提供了指令级的CAS原子操作,比如X86架构下的cmpxchg指令,ARM架构下的LL/SC指令。也就是说,CPU已经原生的支持了CAS,那么上层直接调用即可。

// 乐观锁的实现举例private static AtomicInteger num = new AtomicInteger();
public void testCAS() {for (int i = 0; i < 3; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (num.get() < 1000) {System.out.println(Thread.currentThread().getName() + ":" + num.getAndIncrement());}}});}
}

通过AtomicIteger的源码,发现是使用Unsafe类来实现CAS操作的,这个cas方法是一个native函数,说明是本地方法,和具体的平台实现相关。

5、AQS 机制是什么?

通过上述介绍CAS,我们知道了java通过unsafe 类封装了CAS方法,支持了对CAS原语的调用,但是针对上层业务开发,怎么能够无感知的调用?并且业务场景中,我们最常竞争的资源往往是通过对象进行封装的,而CAS只能原始的修改内存上的一个值。如何进一步对CAS进行抽象呢?

下面就首先介绍一下JUC中经典的同步框架AQS (AbstractQueuedSynchronizer)

5.1 、AQS的成员属性
  1. state是用于判断共享资源是否正在占用的标记为,volatile关键字保证了线程之间的可见性。至于为什么用int类型而不是布尔类型,是因为AQS中有独占锁和共享锁的区别,共享模式下,state可以表示占用锁的线程的数量。
  2. AQS中还存在一个队列,用于管理等待获取锁的线程。FIFO双向链表,head和tail定义了头和尾
    // 队列的头 尾部private transient volatile Node head;private transient volatile Node tail;// 判断共享资源的标志位private volatile int state;
5.2、AQS的内部类

AQS维护了一个FIFO队列,因此定义了一个Node节点,里面存储了线程对象 thread,节点在队列中的等待状态 waitstatus,前后指针等信息。

  • waitStatus:AQS工作的时候,必然伴随着Node 节点状态值的各种变化,这里的waitStatus是一个枚举值
    • 0:节点初始化默认值,或者节点已经释放锁
    • 1:取消获取锁
    • -1:节点的后续节点需要被唤醒
    • -2:条件模式相关
    • -3:共享模式相关,传递共享模式下锁的释放状态
  • predecessor() 方法:获取当前节点的前置Node
     // 简化的,删除了很多东西static final class Node {static final int CANCELLED =  1;static final int SIGNAL    = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}}

5.3、AQS中的核心方法

一般业务,可以分为两种场景使用锁:

  1. 尝试获取锁,不管有没有获取到,立即返回
  2. 必须获取锁,没有获取到则进行等待

恰好,AQS中针对上述两种场景,提供了两个方法,tryAcquire 和 acquire 两个方法。

5.3.1 tryAcquire -- 尝试获取锁

这个方法是参数是一个int 类型的值,代表对 state的增加操作,返回值是boolean,代表是否成功获取锁。该方法只抛出了一个异常,目的就是为了让子类进行重写,在子类中定义相关的业务逻辑,比如没有获取到锁是等待,还是别的处理。

    protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}

5.3.2 acquire -- 必须获取锁

acquire方法被final修饰,无法重写,想要等待并获取锁,直接调用这个方法就可以。

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

if条件包含了两个判断条件,tryAcquire已经说过了,现在讲一下acquireQueue方法。

addWaiter

首先看一下 addWaiter发方法 --- 这个方法作用就是将当前线程封装成一个node,然后加入等待队列中。

    private Node addWaiter(Node mode) {// 封装当前线程为一个NODE节点Node node = new Node(Thread.currentThread(), mode);// 获取当前尾节点Node pred = tail;if (pred != null) {// 先将当前节点前指针指向尾节点node.prev = pred;// CAS判断当前尾节点是否还是尾节点,如果是,就把当前节点变为尾节点,然后返回出去if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 如果当前队列为空,或者CAS失败,就进入这个方法enq(node);return node;}private Node enq(final Node node) {// 自旋 ,如果队列没有初始化,那么就初始化,如果尾节点插入失败,就不断重试,直到插入为止for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

既然将node加入队列成功了,后面肯定还会从队列中取出节点,一般这种FIFO队列都会使用 “生产者-消费者”模式,但是AQS却不是这么使用的,我们接着往下看。

【acquireQueued】方法

  1. 首先定义了一个failed变量,默认是true,如果当前线程正常获取到了锁,这个值就改为false,finally语句块里面的方法只是为了解决异常情况下,取消当前线程获取锁的行为
  2. 在AQS中,头节点head是个虚节点,即队列中的第一个节点的前一个节点是头节点
    1. 首先只有队列中的第一个节点,才有权限尝试获取锁,如果获取到锁,进入if中,然后返回结果,如果没有获取到锁,就进入下一个判断。
    2. 第二个if看判断条件,从名字上来看,首先判断当前线程是否需要挂起,如果需要挂起,就执行挂起操作,如果不需要,就继续自旋获取锁。
  3. 【shouldParkAfterFailedAcquire】判断获取锁失败后,是否需要挂起
    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();// 如果是队列中的第一个节点,并且获取到了锁,就进入这个if中if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 判断是否需要挂起,如果需要挂起就执行挂起操作,否则下一次for循环if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)// 取消当前线程获取锁的行为。处理try中的异常情况cancelAcquire(node);}}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)// ws为-1,当前节点也在等待拿锁,因此可以挂起休息return true;if (ws > 0) {// 表示获取锁请求被取消了,就从当前队列中删除do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 将当前节点状态改为-1,然后外层重试compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}private final boolean parkAndCheckInterrupt() {// 这一行就是执行了挂起操作。是通过unsafe的native方法操作系统原语LockSupport.park(this);// 被唤醒之后,返回当前线程有没有被中断。return Thread.interrupted();}

通过对acquireQueued 方法的分析,可以说,这个方法就是将当前队列中的线程节点都挂起,避免不必要的自旋浪费CPU资源。

既然有线程被挂起,那么就需要将这些挂起的线程唤醒,当持有锁的线程释放了锁,那么就会尝试唤醒后续的节点,AQS中提供了release方法用来唤醒挂起的线程。

5.3.3 tryRelease方法

和tryAcquire方法一样,tryRelease方法也是开放给上层实现的。

    protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}
5.3.4  release 方法

release方法中,假设尝试释放锁成功,下一步就要唤醒等待队列中的其他节点,unparkSuccessor方法中传入的是头节点。

【unparkSuccessor】方法

  1. 设置头节点的状态为0,表示已经释放了锁
  2. 获取队列中最靠前的一个准备获取锁的节点(状态不能是canceled:取消获取锁)
  3. 将这个节点唤醒,去争抢锁。
    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}private void unparkSuccessor(Node node) {// 获取头节点的状态,如果不是0,就修改为0,表示锁已经释放了int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 获取头节点的后续节点Node s = node.next;// 如果为空,或者处于canceled状态,那么就从后往前搜索,找到除head外最靠前的nodeif (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}// 唤醒这个node,让他起来尝试拿锁if (s != null)LockSupport.unpark(s.thread);}

【注意】 unparkSuccessor中搜索节点时是从后往前搜的,为什么这样操作呢?

我们前面介绍了addWaiter方法,后节点的pre指针先指向前节点,前节点的next指针才会指向后节点,这两个步骤不是原子性操作,因此,如果从前往后搜索,可能前节点的next还没有建立好,那么搜索可能就中断了。

6、ReentrantLock介绍

ReentrantLock被称为可重入锁,是JUC包中并发锁的实现之一,底层调用了AQS,这个锁还包含了公平锁和非公平锁的特性

6.1 ReentrantLock的属性

ReentrantLock中只有一个sync属性,sync属性被final修饰,意味着一旦ReentrantLock被初始化,sync属性就不可修改了。

ReentrantLock提供了两个构造器,通过名字可以看出来,我们可以通过传参的形式将ReentrantLock实例化为公平锁 或者是非公平锁。

    private final Sync sync;// 默认构造器,初始化为非公平锁public ReentrantLock() {sync = new NonfairSync();}// 入参为true的时候是公平锁,否则是非公平锁public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
6.2 ReentrantLock的内部类  - Sync

上面说了ReentrantLock存在一个Sync类型的构造器,那么一起看看这个内部类吧,这个内部类没有属性,除了lock和readObject方法,其余方法都用final修饰了,不希望被外部破坏。

  • 首先Sync内部类继承了AbstractQueuedSynchronizer,说明AQS中机制Sync都可以借用了,Sync被abstrat修饰,说明需要通过子类来进行实例化,NonfairSync / FairSync 后续会做相关介绍。
  • lock() 方法,加锁的抽象方法,需要子类来实现,后续介绍
  • nonfairTryAcquire() 方法,从名字来看,是获取非公平锁,在父类中定义,是因为外层有调用
  • tryRelease方法,返回当前锁是否完全释放
  • 其余的一些方法简单了解即可
    abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;abstract void lock();// 尝试获取非公平锁 final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();// 获取AQS的state属性int c = getState();if (c == 0) {// state属性为0说明锁空闲,通过cas来更改state,// 如果成功则获取到了锁,返回true,否则在下面返回falseif (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// state不为0判断当前线程是否是独占线程---- 这就是可重入锁的实现// 是独占锁,则将state+1,并赋值回去,因此AQS中的state数量其实就是当前线程重入次数else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflow// state是int类型的,16位,小于0就代表溢出了 -- 可重入的最大次数throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// 释放锁 -- 注意返回值是“是否完全释放”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;}// 判断当前线程是否获取到锁protected final boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();}final ConditionObject newCondition() {return new ConditionObject();}// 获取占用锁的线程对象final Thread getOwner() {return getState() == 0 ? null : getExclusiveOwnerThread();}// 返回state的值final int getHoldCount() {return isHeldExclusively() ? getState() : 0;}// 判断锁是否空闲final boolean isLocked() {return getState() != 0;}// 反序列化,可以不关注private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state}}

6.3 ReentrantLock的内部类  - FairSync
  • 公平锁,锁的分配会按照请求锁的顺序,比如按照AQS中的FIFO队列来排队获取锁,就是公平锁的实现。
  • 公平锁能够保证只要你排队了,就一定可以拿到锁。
    static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;// 调用AQS获取锁的逻辑final void lock() {acquire(1);}// 重写AQS的tryAcquire方法protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 锁空闲,并且AQS队列中没有其他节点,那么就尝试取锁 --- 体现了公平性if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 如果获取锁的是当前线程,就state+1 -- 可重入int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}}
6.4 ReentrantLock的内部类  - NonFairSync
  • 非公平锁:获取锁不会按照锁请求顺序,比如抢占式
  • 非公平锁不会保证排队的线程一定会获取锁,某个线程可能一直处于阻塞状态,称为“饥饿”
  • 为什么设计非公平锁呢? --- 某些时候,唤醒已经挂起的线程,这个线程的状态切换会产生短暂的延时,而这个延时时间可能就够进行一次业务处理,因此非公平锁可利用这段时间完成操作,提高效率。
    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;// 加锁,先尝试进行一次CAS,失败则进入队列排队// 因为AQS的acquire方法中调用了tryAcquire,下面又重写了tryAcquire方法// 结合nonfairTryAcquire方法代码,可以看到,非公平锁是先两次尝试获取锁,失败之后在排队拿锁final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// 直接调用父类的nonfairTryAcquire方法。protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}

6.5 ReentrantLock的公共方法 -  lock / tryLock / unlock
  1. lock方法 : 加锁的方法很简单,就是通过sync属性去调用,sync的lock方法在上述内部类中都已经介绍了。-- 多态
  2. tryLock方法:无论sync的实现是否是公平锁,tryLock的实现都是非公平的 --- nonfairTryAcquire方法写在Sync中的原因
  3. unlock :释放锁操作,每次对于每次执行,state-1
    public void lock() {sync.lock();}// 无论sync的实现是否是公平锁,tryLock的实现都是非公平的public boolean tryLock() {return sync.nonfairTryAcquire(1);}// 含参的,给了个超时时间public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}public void unlock() {sync.release(1);}
6.6  ReentrantLock 类的使用介绍
6.6.1 使用ReentrantLock进行普通加锁

功能类似于synchronized关键字,获取到锁的线程释放锁之后,其他线程才可以获取到锁。

public class TestReentrantLock {private static final Lock lock = new ReentrantLock();public void testLock() {try {lock.lock();System.out.println("Thread name: " + Thread.currentThread().getName() + " lock");Thread.sleep(2000);lock.unlock();System.out.println("Thread name: " + Thread.currentThread().getName() + " unlock");} catch (InterruptedException e) {throw new RuntimeException(e);}}public static void main(String[] args) {TestReentrantLock testLock = new TestReentrantLock();// 起两个线程,第一个线程释放锁之后,第二个线程才可以获取到锁new Thread(new Runnable() {@Overridepublic void run() {testLock.testMethod();}}).start();new Thread(new Runnable() {@Overridepublic void run() {testLock.testMethod();}}).start();}
}------print------------
Thread name: Thread-0 lock
Thread name: Thread-0 un lock
Thread name: Thread-1 lock
Thread name: Thread-1 un lock

6.6.2 使用ReentrantLock实现锁的可入

main方法中先加锁,,然后在解锁前调用testLock方法,因为是在一个线程中,所以不需要释放锁,也可以获取到锁。

public class TestReentrantLock {private static final Lock lock = new ReentrantLock();public void testLock() {try {lock.lock();System.out.println("Thread name: " + Thread.currentThread().getName() + " 加锁");Thread.sleep(2000);lock.unlock();System.out.println("Thread name: " + Thread.currentThread().getName() + " 解锁");} catch (InterruptedException e) {throw new RuntimeException(e);}}public static void main(String[] args) {TestReentrantLock testLock = new TestReentrantLock();// main线程先加锁lock.lock();System.out.println(Thread.currentThread().getName() + " 线程获得了锁");testLock.testLock();lock.unlock();System.out.println(Thread.currentThread().getName() + " 线程释放了锁");}
}------print-----
main 线程获得了锁
Thread name: main 加锁
Thread name: main 解锁
main 线程释放了锁

7、CountDownLatch 介绍

CountDownLatch是JUC工具包中很重要的一个同步工具。CountDownLatch基于AQS, 他的作用是,让某一个线程等待多个线程的操作完成之后,再执行自己的操作。

CountDownLatch定义了一个计数器和一个阻塞队列,当计数器的值递减为0之前,阻塞队列里面的线程,

7.1、CountDownLatch 的使用。
public class DemoCountDownLatch {private final static Random random = new Random();static class SearchTask implements Runnable {private int id;private CountDownLatch latch;public SearchTask(int id, CountDownLatch latch) {this.id = id;this.latch = latch;}@Overridepublic void run() {System.out.println("开始寻找" + id + "号龙珠");int seconds = random.nextInt(10);try {Thread.sleep(seconds * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("找到" + id + "号龙珠" + "花费了" + seconds + "秒时间");/*** 调用countDown() 方法,计数器会减一*/latch.countDown();}}public static void main(String[] args) throws InterruptedException {List<Integer> idList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);/*** CountDownLatch初始化的时候,给定一个整数计数器,不可变*/CountDownLatch latch = new CountDownLatch(idList.size());for (Integer id : idList) {Thread thread = new Thread(new SearchTask(id, latch));thread.start(); // 注意,start 是开启新线程多线程执行,run方法是串行的}/*** 调用 await() 方法时 ,如果计数器大于0,当前线程阻塞,直到计数器被countDown方法减到0时,线程才会继续执行*/latch.await();/*** 调用 await方法时,设置超时参数* 如果计数器大于0,当前线程阻塞,直到计数器被countDown方法减到0时,线程才会继续执行*/// latch.await(3, TimeUnit.SECONDS);System.out.println("所有龙珠找到!召唤神龙!");}
}-----await()   print ------------
----- 子线程都执行完了,主线程才开始执行 ----------
开始寻找1号龙珠
开始寻找3号龙珠
开始寻找2号龙珠
开始寻找5号龙珠
开始寻找6号龙珠
开始寻找4号龙珠
开始寻找7号龙珠
找到3号龙珠花费了3秒时间
找到1号龙珠花费了3秒时间
找到7号龙珠花费了5秒时间
找到6号龙珠花费了5秒时间
找到2号龙珠花费了6秒时间
找到4号龙珠花费了6秒时间
找到5号龙珠花费了8秒时间
所有龙珠找到!召唤神龙!-----await(3, TimeUnit.SECONDS)   print  ------------
--- 设置了三秒超时,三秒之后无论子线程有没有全部结束,都执行主线程 ------
开始寻找1号龙珠
开始寻找3号龙珠
开始寻找2号龙珠
开始寻找4号龙珠
开始寻找5号龙珠
开始寻找7号龙珠
开始寻找6号龙珠
找到5号龙珠花费了3秒时间
所有龙珠找到!召唤神龙!
找到6号龙珠花费了5秒时间
找到7号龙珠花费了8秒时间
找到2号龙珠花费了8秒时间
找到1号龙珠花费了8秒时间
找到4号龙珠花费了9秒时间
找到3号龙珠花费了9秒时间-----不执行await   print ------------
--- 主线程直接执行,不等待子线程 ------
开始寻找3号龙珠
所有龙珠找到!召唤神龙!
开始寻找4号龙珠
开始寻找5号龙珠
开始寻找6号龙珠
开始寻找2号龙珠
开始寻找7号龙珠
开始寻找1号龙珠
找到1号龙珠花费了2秒时间
找到3号龙珠花费了3秒时间
找到4号龙珠花费了4秒时间
找到2号龙珠花费了5秒时间
找到5号龙珠花费了6秒时间
找到6号龙珠花费了9秒时间
找到7号龙珠花费了9秒时间

7.2、CountDownLatch设计思路?

CountDownLatch,我们就简单的描述为,主线程等待子线程处理完任务之后在继续执行自己的任务。既然主线程在等待,根据前面学习的AQS,此时主线程应该放入等待队列中,那么什么时候唤醒主线程呢?当然是子任务都执行结束之后,那么AQS中的state就可以派上用场了,state表示子线程的数量(也就是主线程需要等待的线程数目),每当一个任务完成了,state就减去1,当state值为0 的时候,就唤醒正在等待的主线程。

CountDownLatch 的设计思路大致就是上面介绍的,下面会根据源码来进行剖析。

7.2.1 sync内部类

CountDownLatch内部也定义了一个sync内部类,并继承了AQS;

【tryAcquireShared】方法,这个方法是尝试获取共享锁,是对AQS方法的一个重写。

这个方法很简单,获取state的值,如果等于0,就返回1,否则返回-1;


子类对父类方法的重写,也是要按照约定去重写的,我们在看看AQS中对tryAcquireShared方法的定义:

  • 返回负值:获取锁失败
  • 返回0:共享模式下获取锁成功,不唤醒后续节点
  • 返回整数:获取锁成功,并唤醒后续节点

从父类方法定义来看,如果tryAcquireShared方法返回整数,是需要获取锁的,但是子类实现的方法并没有获取锁的操作,这个是为什么呢?

----

实际上,我们需要从CountDownLatch这个组件需要解决的问题来看待,当CountDownLatch被初始化时,必须传入一个count,这个值会赋给aqs的state,每当一个子任务完成,state值就会减一,一直到state为0,然后主任务继续操作。这其实就是一个子任务不断释放锁,主任务不断检查锁有没有完全释放的过程,所有的操作不涉及到加锁的情况,虽然主任务在state为0的时候也可以加锁,但是完全没有必要。

【tryReleaseShared】方法,就是一个释放锁的操作,

锁完全释放返回true,否则返回false

    private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;// 构造方法,需要传入一个count值Sync(int count) {setState(count);}// 获取当前count值int getCount() {return getState();}// 对AQS的重写,从名字看,方法被shared修饰,应该是用到了共享模式// 共享模式下,state可以被多个线程同时修改,加1代表获取共享锁,减1代表释放共享锁// 这个方法就是如果state为0,代表子任务全部完成,否则就是还有没完成的protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}// 释放锁的操作,一个自旋的过程// 不需要释放锁或者没有完全释放锁,返回false// 锁完全释放了,返回trueprotected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}
7.2.2 内部属性和构造器

内部属性只有一个sync,只有一个有参数构造器,必须传递一个大于0的整数(主线程需要等待子线程的数量。)

    private final Sync sync;public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}
7.2.3 await 方法

这个方法就是主线程等待子线程执行完的逻辑。

    public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}// AQS的方法public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 锁没有完全释放的情况,也就是主线程等待子线程的场景if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// 设置共享模式的nodefinal Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();// 当前节点的前置节点为head,说明当前节点可以被唤醒if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {// 到这个判断里面说明锁已经空闲了,也就是子任务都执行结束了setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check belowsetHead(node);// 传播行为大于0,唤醒后续节点// 被唤醒的节点会在doAcquireSharedInterruptibly方法中的for循环继续执行// 不断的唤醒队列中处于等待状态的且共享模式的线程。if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;if (s == null || s.isShared())doReleaseShared();}}
7.2.4 countDown方法

就是为了保证state的自减操作,调用此方法,state减1

    public void countDown() {sync.releaseShared(1);}public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

相关文章:

并发编程 java锁机制

1、什么是锁&#xff0c;为什么需要锁&#xff1f; 并发环境下&#xff0c;会存在多个线程对同一个资源进行争抢的情况&#xff0c;假设线程A对资源正在进行修改&#xff0c;此时线程B又对同一资源进行了修改&#xff0c;就会导致数据不一致的问题。为了解决这个问题&#xff…...

Onerugged三防平板厂家丨三年质保承诺丨三防平板PAD

行业领先产品——Onerugged三防平板。凭借着十年的经验&#xff0c;我们深知终端设备在各个行业中的重要性&#xff0c;因此致力于为用户提供高可靠性的解决方案。 Onerugged三防平板以其卓越的性能和全方位的保护功能&#xff0c;在市场上脱颖而出。首先&#xff0c;它拥有IP…...

Android 系统启动流程

一.Android系统启动流程基本框架 Android系统完整的启动过程&#xff0c;从系统层次角度可分为 Linux 系统层、Android 系统服务层、Zygote进程模型三个阶段&#xff1b;从开机到启动 Home Launcher 完成具体的任务细节可分为七个步骤&#xff0c;下面就从具体的细节来解读 And…...

鸿蒙学习-app.json5配置文件

官网文档参考&#xff1a;https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/app-configuration-file-0000001427584584-V3 位于AppScope下的app.json5配置文件 一、基础属性 {"app": {/*包名*/"bundleName": "com.example.dem…...

华为OD机试 - 智能成绩表( Python C C++ JavaGo JS PHP)

题目描述 小明是一名新老师&#xff0c;他需要将学生按考试总分或单科分数进行排名。学生的信息包括姓名、科目和对应的分数。帮助小明完成这个任务吧&#xff01; 输入描述 第一行包含两个整数 n 和 m&#xff0c;分别代表学生人数和科目数量。 0 < n < 1000 < m &…...

训练集,验证集,测试集比例

三者的区别 训练集&#xff08;train set&#xff09; —— 用于模型拟合的数据样本。验证集&#xff08;validation set&#xff09;—— 是模型训练过程中单独留出的样本集&#xff0c;它可以用于调整模型的超参数和用于对模型的能力进行初步评估。 通常用来在模型迭代训练时…...

Altium Designer(AD)加载常用元器件库到工程图文教程及视频演示

🏡《专栏目录》 目录 视频演示1,概述2,加载方法3,总结视频演示 Altium Designer(AD)加载常用元器件库到工程 欢迎点击浏览更多高清视频演示 1,概述...

Java学习笔记2024/2/8

面向对象 //面向对象介绍 //面向: 拿、找 //对象: 能干活的东西 //面向对象编程: 拿东西过来做对应的事情 //01-如何设计对象并使用 //1.类和对象 //2.类的几个不错注意事项 1. 类和对象 1.1 类和对象的理解 客观存在的事物皆为对象 &#xff0c;所以我们也常常说万物皆对…...

【安防】三个问题:IPC和ITC主要的差异点和相同点 、影响图像成像效果的因素有哪些、摩尔纹如何产生的和消除方法

问题一、IPC和ITC主要的差异点和相同点 差异点 1、应用场景&#xff1a;IPC主要应用于普通安防监控领域&#xff0c;如广场、商场、公园、写字楼等。它们通常被用于监控室内或有限区域的安全&#xff0c;例如&#xff0c;监控办公室、仓库、门口等。而ITC则主要应用于交通领…...

Windows 安装 MySQL 最新最简教程

Windows 安装 MySQL 最新最简教程 官网地址 https://dev.mysql.com/downloads/mysql/下载 MySQL zip 文件 配置 MySQL1、解压文件 2、进入 bin 目录 搜索栏输入 cmd 回车进入命令行 C:\Users\zhong\Desktop\MySQL\mysql-8.3.0-winx64\mysql-8.3.0-winx64\bin 注意这里是你自己…...

uniapp 本地存储的方式

1. uniapp 本地存储的方式 在uniapp开发中&#xff0c;本地存储是一个常见的需求。本地存储可以帮助我们在客户端保存和管理数据&#xff0c;以便在应用程序中进行持久化存储。本文将介绍uniapp中本地存储的几种方式&#xff0c;以及相关的代码示例。 1.1. 介绍 在移动应用开发…...

25、数据结构/二叉树相关练习20240207

一、二叉树相关练习 请编程实现二叉树的操作 1.二叉树的创建 2.二叉树的先序遍历 3.二叉树的中序遍历 4.二叉树的后序遍历 5.二叉树各个节点度的个数 6.二叉树的深度 代码&#xff1a; #include<stdlib.h> #include<string.h> #include<stdio.h> ty…...

数据结构——D/二叉树

&#x1f308;个人主页&#xff1a;慢了半拍 &#x1f525; 创作专栏&#xff1a;《史上最强算法分析》 | 《无味生》 |《史上最强C语言讲解》 | 《史上最强C练习解析》 &#x1f3c6;我的格言&#xff1a;一切只是时间问题。 ​ 1.树概念及结构 1.1树的概念 树是一种非线性的…...

redis:七、集群方案(主从复制、哨兵模式、分片集群)和面试模板

redis集群方案 在Redis中提供的集群方案总共有三种&#xff08;一般一个redis节点不超过10G内存&#xff09; 主从复制哨兵模式分片集群 主从复制&#xff08;主从数据同步&#xff09; replid和offset Replication Id&#xff1a;简称replid&#xff0c;是数据集的标记&a…...

没有事情做 随手写的小程序

Qt 代码包 在百度网盘里 链接: https://pan.baidu.com/s/17yjeAkzi18upfqfD7KxXOQ?pwd6666 dialog.h : #ifndef DIALOG_H #define DIALOG_H#include <QDialog> #include <mythread.h>QT_BEGIN_NAMESPACE namespace Ui { class Dialog; } QT_END_NAMESPACEclas…...

简单说网络:TCP+UDP

TCP和UPD: (1)都工作在传输层 (2)目的都是在程序之中传输数据 (3)数据可以是文本、视频或者图片(对TCP和UDP来说都是一堆二进制数没有太大区别) 一、区别:一个基于连接一个基于非连接 将人与人之间的通信比喻为进程和进程之前的通信:基本上有两种方式(1)写信;(2)打电话;这…...

Containerd 的前世今生和保姆级入门教程

Containerd 的前世今生 很久以前&#xff0c;Docker 强势崛起&#xff0c;以“镜像”这个大招席卷全球&#xff0c;对其他容器技术进行致命的降维打击&#xff0c;使其毫无招架之力&#xff0c;就连 Google 也不例外。Google 为了不被拍死在沙滩上&#xff0c;被迫拉下脸面&…...

分享78个行业PPT,总有一款适合您

分享78个行业PPT&#xff0c;总有一款适合您 78个行业PPT下载链接&#xff1a;https://pan.baidu.com/s/19UL58I5Z1QZidVrq50v6fg?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…...

VR全景技术可以应用在哪些行业,VR全景技术有哪些优势

引言&#xff1a; VR全景技术&#xff08;Virtual Reality Panorama Technology&#xff09;是一种以虚拟现实技术为基础&#xff0c;通过360度全景影像、立体声音、交互元素等手段&#xff0c;创造出沉浸式的虚拟现实环境。该技术不仅在娱乐领域有着广泛应用&#xff0c;还可…...

c#cad 创建-点(六)

运行环境 vs2022 c# cad2016 调试成功 一、代码说明 创建一个点的命令方法。代码的主要功能是在当前活动文档中创建一个点&#xff0c;并将其添加到模型空间块表记录中。 代码的主要步骤如下&#xff1a; 获取当前活动文档、数据库和编辑器对象。使用事务开始创建点的过程…...

【JS逆向八】逆向某企查网站的headers参数,并模拟生成 仅供学习

逆向日期&#xff1a;2024.02.07 使用工具&#xff1a;Node.js 加密方法&#xff1a;未知 / 标准库Hmac-SHA512 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 可使用AES进行解密处理&#xff08;直接解密即可&#xff09;&#xff1a;AES加…...

Springboot+vue的社区智慧养老监护管理平台设计与实现(有报告),Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的社区智慧养老监护管理平台设计与实现&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的社区智慧养老监护管理平台设…...

STM32学习笔记——定时器

目录 一、定时器功能概述 1、基本定时器&#xff08;TIM6&TIM7&#xff09; 工作原理 时序 2、通用计时器&#xff08;TIM2&TIM3&TIM4&TIM5&#xff09; 时钟源 外部时钟源模式1&2 外部时钟源模式2 外部时钟源模式1 定时器的主模式输出 输入捕获…...

Android编程权威指南(第四版)- 第 4 章 UI状态的保存与恢复

文章目录 代码:依赖MainActivityQuizViewModelQuestion知识点代码: 大体是一样的,修改了一些 依赖 implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")MainActivity package com.example.geoquizimport androidx.appcompat.app.AppCompatActivi…...

代理模式

如有错误或有补充&#xff0c;以及任何改进的意见&#xff0c;请留下您的高见 定义 代理模式是一种设计模式&#xff0c;它为其他对象提供一种代理以控制对这个对象的访问。代理模式是一种结构型模式&#xff0c;它可以在不修改源码的情况下增强方法&#xff0c;在方法前后增…...

C++三剑客之std::any(一) : 使用

相关系列文章 C三剑客之std::any(一) : 使用 C之std::tuple(一) : 使用精讲(全) C三剑客之std::variant(一) : 使用 C三剑客之std::variant(二)&#xff1a;深入剖析​​​​​​​ 目录 1.概述 2.构建方式 2.1.构造函数 2.2.std::make_any 2.3.operator分配新值 3.访问值…...

2024年:用OKR管理你的生活

在科技高速发展的时代&#xff0c;越来越多的企业和团队开始采用OKR&#xff08;Objectives and Key Results&#xff09;管理方法来设定目标并跟踪进度。你是否想过&#xff0c;将OKR理念引入个人生活&#xff0c;以更有效地实现人生目标&#xff1f;本文将探讨如何在2024年运…...

Lua迭代器以及各种源函数的实现

范型for 范型for的格式如下所示&#xff1a; for <var-list> in <exp-list> do<body> end var-list指变量名列表&#xff0c;可以为多个&#xff0c;exp-list指表达式列表&#xff0c;通常情况下只有一个值。可以更具体地写为另一种形式&#xff1a; fo…...

e5 服务器具备哪些性能特点?

随着云计算和大数据技术的不断发展&#xff0c;服务器作为数据中心的核心设备&#xff0c;其性能特点也日益受到关注。其中&#xff0c;E5服务器作为当前主流的服务器类型之一&#xff0c;具备许多优秀的性能特点。本文将详细介绍E5服务器的性能特点&#xff0c;帮助读者更好地…...

《C++ Primer Plus》《2、开始学习C++》

文章目录 0 前言&#xff1a;1 进入C1.1 main()函数1.2 C注释1.3 预处理器和iostream1.4 头文件名1.5 名称空间1.6 使用cout进行C输出1.7 C源代码的格式化 2 C语句2.1 声明语句和变量2.2 赋值语句2.3 cout语句 3 其他C语句3.1使用cin3.2 使用cout进行拼接3.3 类简介 4 函数4.1 …...

Backtrader 文档学习- Sizers

Backtrader 文档学习- Sizers 1.概述 智能仓位 Strategy提供了交易方法&#xff0c;即&#xff1a;buy&#xff0c;sell和close。看一下buy的定义&#xff1a; def buy(self, dataNone,sizeNone, priceNone, plimitNone,exectypeNone, validNone, tradeid0, **kwargs):注意&…...

基于YOLOv8算法的照片角度分类项目实践

目录 一、任务概述二、YOLOv8算法简介2.1 算法改进2.2 算法特点2.3 网络结构2.4 性能比较 三、工程实践3.1 安装算法框架库ultralytics3.2 库存照片预处理3.2.1 提取所有图片3.2.2 去除冗余的相同照片3.2.3 去除无车辆照片3.2.4 随机提取指定数量的图片 3.3 照片朝向分类3.3.1 …...

go语言进阶篇——面向对象(一)

什么是面向对象 在我们设计代码时&#xff0c;比如写一个算法题或者写一个问题结局办法时&#xff0c;我们常常会使用面向过程的方式来书写代码&#xff0c;面向过程主要指的是以解决问题为中心&#xff0c;按照一步步具体的步骤来编写代码或者调用函数&#xff0c;他在问题规…...

C#,栅栏油漆算法(Painting Fence Algorithm)的源代码

1 刷油漆问题 给定一个有n根柱子和k种颜色的围栏&#xff0c;找出油漆围栏的方法&#xff0c;使最多两个相邻的柱子具有相同的颜色。因为答案可以是大的&#xff0c;所以返回10^97的模。 计算结果&#xff1a; 2 栅栏油漆算法的源程序 using System; namespace Legalsoft.Tr…...

java_error_in_pycharm.hprof文件是什么?能删除吗?

java_error_in_pycharm.hprof文件是什么&#xff1f;能删除吗&#xff1f; &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;hprof格式文件介绍&#x1f333;&#x1f333;java_error_in_pycharm.hprof文件什么情况下能删除&#x1f333;&…...

LeetCode 491 递增序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列&#xff0c;递增子序列的长度至少是2。 示例: 输入: [4, 6, 7, 7] 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]说明: 给定数组的长度不会超过15。 数组中的整数范围是 [-…...

考研/计算机二级数据结构刷题之顺序表

目录 第一题 顺序表的初始化&#xff0c;销毁&#xff0c;头插&#xff0c;尾插&#xff0c;头删&#xff0c;尾删&#xff0c;指定位置插入&#xff0c;指定删除以及打印 第二题 移除元素 题目链接&#xff1a; OJ链接 题目详解&#xff1a;移除元素 第三题&#xff1a;删…...

Git 代码协同的使用方法 for Azure DevOps

1. 登陆Azure 步骤1.1. VS Code&#xff0c;登陆Azure Cloud的Ubuntu环境&#xff0c;如下&#xff1a; 重点: 这里的Azure Cloud的用户名是YourAzureUserName&#xff0c;口令是YourAzurePassword 步骤1.2. 登陆Azure Cloud的Ubuntu环境后&#xff0c;配置Git账户信息&…...

数据库学习笔记2024/2/5

2. SQL 全称 Structured Query Language&#xff0c;结构化查询语言。操作关系型数据库的编程语言&#xff0c;定义了 一套操作关系型数据库统一标准 2.1 SQL通用语法 在学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的通用语法。 1). SQL语句可以单行或多行书写&…...

PSM-Net根据Stereo图像生成depth图像

一、新建文件夹 在KITTI数据集下新建depth_0目录 二、激活anaconda环境 conda activate pt14py37三、修改submission.py文件 3.1 KITTI数据集路径 parser.add_argument(--datapath, default/home/njust/KITTI_DataSet/00/, helpselect model)3.2 深度图像输出路径 save…...

Mocaverse NFT 概览与数据分析

作者&#xff1a;stellafootprint.network 编译&#xff1a;mingfootprint.network 数据源&#xff1a;Mocaverse NFT Collection Dashboard Mocaverse 是 Animoca Brands 推出的专属 NFT&#xff08;非同质化代币&#xff09;系列&#xff0c;包含 8,888 个独特的 "M…...

SpringBoot之事务源码解析

首先事务是基于aop的&#xff0c;如果不了解aop的&#xff0c;建议先去看下我关于aop的文章: Spring之aop源码解析  先说结论&#xff0c;带着结论看源码。首先&#xff0c;在bean的生命周期中&#xff0c; 执行实例化前置增强&#xff0c;会加载所有切面并放入缓存&#xff0…...

FPGA高端项目:解码索尼IMX327 MIPI相机转USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构FPGA逻辑设计工程源码架构SDK软件工程源…...

基于高通滤波器的ECG信号滤波及心率统计matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 ECG信号简介 4.2 高通滤波器原理 4.3 心率统计 5.完整工程文件 1.课题概述 通过高通滤波器对ECG信号进行滤波&#xff0c;然后再统计其心率。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a…...

springCould中的gateway-从小白开始【9】

目录 1.&#x1f35f;网关是什么 2.&#x1f37f;gateway是什么 3.&#x1f95a;gateway能什么 4.&#x1f32d;核心概念 5.&#x1f9c2;工作流程 6.&#x1f9c8;实例 7.&#x1f953;gateway网关配置的方式 8.&#x1f373;配置动态路由 9.&#x1f9c7;pred…...

邮箱营销软件推荐?企业邮箱群发会限制吗?

邮箱营销平台怎么选择&#xff1f;哪种邮箱适合做外贸邮件群发&#xff1f; 邮箱营销凭借其精准触达、低成本和高回报的特点&#xff0c;依然是许多企业不可或缺的营销手段。该如何选择一款适合自己的工具呢&#xff1f;蜂邮EDM将为您推荐几款优秀的邮箱营销软件&#xff0c;并…...

编译原理实验3——自下而上的SLR1语法分析实现(包含画DFA转换图、建表、查表)

文章目录 实验目的实现流程定义DFA状态实现代码运行结果测试1测试2测试3 总结 实验目的 实现自下而上的SLR1语法分析&#xff0c;画出DFA图 实现流程 定义DFA状态 class DFA:def __init__(self, id_, item_, next_ids_):self.id_ id_ # 编号self.item_ item_ # productio…...

基于tomcat的https(ssl)双向认证

一、背景介绍 某个供应商服务需要部署到海外&#xff0c;如果海外多个地区需要部署多个服务&#xff0c;最好能实现统一登录&#xff0c;这样可以减轻用户的使用负担&#xff08;不用记录一堆密码&#xff09;。由于安全问题&#xff08;可能会泄露用户数据&#xff09;&#x…...

【iOS ARKit】3D人体姿态估计实例

与2D人体姿态检测一样&#xff0c;在ARKit 中&#xff0c;我们不必关心底层的人体骨骼关节点检测算法&#xff0c;也不必自己去调用这些算法&#xff0c;在运行使用 ARBodyTrackingConfiguration 配置的 ARSession 之后&#xff0c;基于摄像头图像的3D人体姿态估计任务也会启动…...

ROS2 CMakeLists.txt 和 package.xml

这里记录一下ROS2中功能包package.xml和CMakeLists.txt的格式。以LIO-SAM的ROS2版本为例&#xff1a; 一&#xff1a;CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(lio_sam)if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)set(CMAKE_BUILD_TYPE…...