JUC锁与AQS技术【我的Android开发技术】
JUC锁与AQS技术【我的Android开发技术】
AQS原理
AQS就是一个同步器,要做的事情就相当于一个锁,所以就会有两个动作:一个是获取,一个是释放。获取释放的时候该有一个东西来记住他是被用还是没被用,这个东西就是一个状态。如果锁被获取了,也就是被用了,还有很多其他的要来获取锁,总不能给全部拒绝了,这时候就需要他们排队,这里就需要一个队列。这大概就清楚了AQS的主要构成了:
- 获取和释放两个动作
- 同步状态(原子操作)
- 阻塞队列
Lock特性
锁机制用于保证操作的原子性、可见性、顺序性。 JDK1.5的concurrent并发包 中新增了 Lock接口以及相关实现类来实现锁功能 ,最明显的特性就是需要显式的申请锁和释放锁。比synchronized更加灵活。
显示锁的释放锁的操作一定要放到finally块中,否则可能会因为异常导致锁永远无法释放!这是显式锁最明显的缺点。
1.1.显示加锁、解锁
- synchronized 关键字是自动进行加锁、解锁的,而Lock的具体实现需要 lock() 和 unlock()方法配合 try/finally 语句块来完成,来手动加锁、解锁。
1.1.可重入
像 synchronized 和 ReentrantLock 都是可 重入锁 ,可重入性表明 了锁的分配机制是 基于线程的分配 ,而 不是基于方法调用 的分配。
- 可重入锁:又名 递归锁 ,即一个线程得到一个对象锁后再次请求该对象锁,是永远可以拿到锁的。在 Java 中线程获得对象锁的操作是 以线程为单位的 ,而不是以方法调用为单位的。但 获取锁和释放锁必须要成对出现 。
- 如 ReentrantLock ,它是基于 AQS(AbstractQueueSyncronized) 实现的, AQS 是基于 volitale 和 CAS 实现的 ,其中 AQS 中维护一个 valitale 类型的变量 state 来做一个可重入锁的重入次数,加锁和释放锁也是围绕这个变量来进行的 。
1.2.可响应中断
- 当线程因为获取锁而进入 阻塞状态 , 外部是可以中断该线程的 , 调用方通过捕获nterruptedException可以捕获中断
- 如 ReentrantLock 中的 lockInterruptibly() 方法 可以使线程在被 阻塞 时响应中断
假设: 线程1 通过 lockInterruptibly() 方法获取到一个可重入锁,并执行一个长时间的任务,另一个 线程2 通过 interrupt() 方法 就可以立刻 打断 线程1的执行 ,来获取 线程1 持有的那个可重入锁。而通过 ReentrantLock 的 lock() 方法或者 Synchronized 持有锁的线程是不会响应其他线程的 interrupt() 方法的,直到该方法主动释放锁之后才会响应 interrupt() 方法。
1.3.可设置等待超时时间
- synchronized 关键字无法设置锁的超时时间,如果一个获得锁的线程内部发生死锁,那么其他线程就会一直进入阻塞状态, 而 Lock的具体实现,可以 设置线程获取锁的等待超时时间 ,通过 方法返回值 判断是否成功获取锁,来 避免死锁
1.4.锁的公平性
提供公平锁和非公平锁2种选择。
-
公平锁( 默认 ) :线程将按照他们发出 发出申请锁的顺序 来获取锁,先进先出, 不允许插队
-
非公平锁: 允许插队 ,当一个线程请求获取锁时,如果这个锁是 可用 的,那这个线程将 跳过所在队列里等待线程并获得锁。
- 如: synchronized 关键字是一 种非公平锁 ,先抢到锁的线程先执行。而 ReentrantLock的构造方法中允许设置 true/false 来实现公平、非公平锁 ,如果设置为 true ,则线程获取锁要遵循 “先来后到” 的规则,每 次都会构造一个 线程 Node ,然后到双向链表的 "尾巴"后面排队,等待前面的 Node 释放锁资源。
- 考虑这么一种情况:A线程持有锁,B线程请求这个锁,因此B线程被挂起;A线程释放这个锁时,B线程将被唤醒,因此再次尝试获取锁; 与此同时,C线程也请求获取这个锁,那么C线程很可能在B线程被完全唤醒之前 获得、使用以及释放 这个锁。这是种双赢的局面,B获取锁的时刻(B被唤醒后才能获取锁)并没有推迟,C更早地获取了锁,并且吞吐量也获得了提高。 在大多数情况下,非公平锁的性能要高于公平锁的性能。
另外,这个公平性是针对 线程 而言的,不能依赖此来实现业务上的公平性,应该由开发者自己控制,比如通过 FIFO队列 来保证公平。
1.5.读写锁
-
允许读锁和写锁分离,读锁与写锁互斥,但是多个读锁可以共存, 即一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行 。适用于 读远大于写 的场景
-
即:读读共享、写写互斥、读写互斥
关于读写锁的一些知识:
-
1.重入方面其内部的写锁可以获取读锁,但是反过来读锁想要获得写锁则永远都不要想。
-
2.写锁可以降级为读锁,顺序是: 先获得写锁再获得读锁,然后释放写锁 ,这时候线程将保持读锁的持有。反过来读锁想要升级为写锁则不行
-
3. 读锁被线程持有时排斥任何的写锁,而线程持有写锁则是完全的互斥.这一特性最为重要, ,对于 读多写少 的场景使用此类才可以提高并发量。
-
4.不管是读锁还是写锁都支持 响应中断
-
5.写锁支持Condition且用于与ReentrantLock一样, 而读锁则不能使用Condition ,否则抛出UnsupportedOperationException异常。
1.6.丰富的API
提供了多个方法来获取锁相关的信息,可以帮助开发者监控和排查问题
- isFair() :判断锁是否是公平锁
- isLocked() :判断锁是否被任何线程获取了
- isHeldByCurrentThread() :判断锁是否被当前线程获取了
- hasQueuedThreads() :判断是否有线程在等待该锁
- etHoldCount() :查询当前线程占有lock锁的次数
- getQueueLength() :获取正在等待此锁的线程数
1.7.常用方法
void lock() :在线程获取锁时如果锁已被其他线程获取,则进行 等待
Lock lock = new ReentrantLock();//获取锁
lock.lock();
try{//获取到了被本锁保护的资源,处理任务//捕获异常
}finally{lock.unlock(); //释放锁
}
- boolean tryLock() : 尝试获取锁 ,如果当前锁没有被其他线程占用,则获取成功,返回 true,否则返回 false
我们可以根据是否能获取到锁来决定后续程序的行为。该方法会立即返回,在拿不到锁时也不会一直等待,通常我们用 if 语句 判断 tryLock() 的返回结果 , 根据是否获取到锁来执行不同的业务逻辑 ,使用方法如下。
Lock lock = new ReentrantLock();//获取锁
//如果能获取到锁
if(lock.tryLock()) {try{//处理任务}finally{lock.unlock(); //释放锁}
}
//如果不能获取锁,则做其他事情
else {}利用 tryLock() 方法我们还可以解决死锁问题public void tryLock(Lock lock1, Lock lock2) throws InterruptedException {//自旋while (true) {//如果能获取锁1if (lock1.tryLock()) {try {//如果能获取锁2if (lock2.tryLock()) {try {System.out.println("获取到了两把锁,完成业务逻辑");return;} finally {//释放锁2lock2.unlock();}}} finally {//释放锁1lock1.unlock();}}//如果不能能获取锁,线程休眠若干秒 else {Thread.sleep(new Random().nextInt(1000));}}}
- boolean tryLock(long time, TimeUnit unit): 可响应中断并且有超时时间的尝试获取锁 ,在拿不到锁时会等待一定的时间,如果在时间结束后,还获取不到锁,就会返回 false;如果一开始就获取锁或者等待期间内获取到锁,则返回 true。
这个方法解决了 lock() 方法容易发生死锁的问题,使用 tryLock(long time, TimeUnit unit) 时 ,在等待了一段指定的超时时间后, 线程会主动放弃获取这把锁 ,避免永久等待。 等待获取锁的期间,也可以 随时中断线程 ,这就避免了死锁的发生。
- void lockInterruptibly(): 可响应中断的去获取锁 ,如果这个锁当前是可以获得的,那么这个方法会立刻返回,但是如果这个锁当前是不能获得的(被其他线程占用),那么当前线程便会开始 等待 ,除非它 等到了这把锁或者是在等待的过程中被中断了 ,否则这个线程便会一直在这里执行这行代码。一句话总结就是, 除非当前线程在获取锁期间被 中断 ,否则便会 一直尝试获取 直到获取到为止。
顾名思义, lockInterruptibly() 是可以 响应中断 的。相比于不能响应中断的 synchronized 锁 , lockInterruptibly() 可以让程序更灵活,可以在获取锁的同时, 保持对中断的响应 。我们可以把这个方法理解为 超时时间是无穷长的 tryLock(long time, TimeUnit unit) ,因为 tryLock(long time, TimeUnit unit) 和 lockInterruptibly() 都能响应中断 ,只不过 lockInterruptibly() 永远不会超时。
public void lockInterruptibly() {try {lock.lockInterruptibly();try {System.out.println("操作资源");} finally {lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();} finally {//释放锁lock.unlock();}}
- void unlock() :释放当前线程占用的锁, 必须使用finally块保证发生异常时锁一定被释放
- Condition newCondition() :创建绑定到此 Lock 实例的 Condition实例 ,用于替代wait()/notify()/notifyAll()方法, 实现线程的等待通知机制
Condition对象的await()/signal()/signalAll() 的功能和wait()/notify()/notifyAll()一样
1. Lock接口
在Java5之前,要想使用锁来保护共享资源大多数情况是使用synchronized关键字。在Java5之后,并发包里增加了Lock接口及其实现类来提供锁的功能。
Lock接口与synchronized有许多区别。synchronized修饰在方法体或者代码块上可以隐式地获取和释放锁,并且锁地获取和释放操作被固化。但是Lock接口可以显示地获取和释放锁,提高了锁的可操作性。并且Lock接口支持可中断锁以及超时锁等synchronized没有的特性。
Lock接口的使用范式如下:
1 Lock lock = new ReentrantLock();
2 lock.lock();
3 try{
4 //访问临界区
5 }finally{
6 lock.unlock();
7 }
Lock接口的实现类能实现锁的功能,靠的是聚合AQS的子类作为同步器,将提供给用户的锁的操作委托给这个同步器执行。
2. 队列同步器(AQS)
AQS是并发包里同步组件的核心基础,可以用来构建锁和其他同步器件。
AQS维护一个int型的同步状态属性,利用这个同步状态属性可以实现独占锁和共享锁,以及如信号量等同步组件。比如要实现独占锁,则同步状态往往初始化为1。另外AQS内部还定义了一个静态内部类Node。Node组成了AQS的同步队列和等待队列,Node里保存了线程信息,等待状态等。AQS维护同步队列的头结点head和尾结点tail,并负责同步队列的操作。
AQS采用模板方法的设计模式,提供了独占和共享式获取释放同步状态的方法,以及可中断和超时获取同步状态的方法。这些模板方法调用了一些抽象方法。AQS里的抽象方法由继承它的子类根据需要实现。自定义同步组件往往会声明一个继承AQS的静态内部类,称为同步器。并将Lock接口提供给用户调用的方法委托给同步器处理。这样做的好处有两个方面,第一个是对同步组件的使用者隐藏了实现细节,用户只需要调用Lock接口提供的方法就可以获得锁的功能,而不知道具体的执行是由同步器来完成。第二个是向同步组件的开发者提供了便捷的开发接口,隐藏了底层操作系统的线程管理等细节。
2.1 AQS的同步状态操作
利用AQS设计同步组件的关键在于同步状态。AQS提供了三个方法保证线程安全的修改同步状态
1 private volatile int state;2 protected final void setState(int newState) {3 state = newState;4 }5 protected final int getState() {6 return state;7 }8 protected final boolean compareAndSetState(int expect, int update) {9 // See below for intrinsics setup to support this
10 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
11 }
可以看到同步状态的线程安全是基于volatile的可见性和禁止重排序,以及利用CAS原子性替换。
2.2 AQS的实现分析
2.2.1 同步队列
AQS里的同步队列(FIFO队列,双向链表)用来完成同步状态的管理。当线程获取同步状态失败时,其线程引用,等待状态,会被封装成Node,并被加入到同步队列,同时AQS会阻塞该线程,直到被同步队列首节点的线程唤醒,并尝试获取同步状态。
AQS维护同步队列的头结点head和尾结点tail,当线程获取同步状态失败时会被加入到同步队列的尾部。
当线程获取同步状态失败时会被构造成Node并插入同步队列尾部,由于同一时刻可能与多个线程要被同时插入到尾部,为了避免出现类似HashMap在多线程环境下链表拉链可能拉成环的情况,AQS使用CAS的方式确保插入尾结点线程安全。
AQS通过addWaiter方法构造Node结点,并将其插入到同步队列尾部,代码如下。首先尝试进行一次快速的插入,利用CAS检查实际尾结点和线程认为的尾结点是否一致,相同则修改结点指针完成插入。如果快速插入失败,则进入enq方法自旋加CAS不断尝试插入的同步队列尾部直到成功。
1 private Node addWaiter(Node mode) {2 Node node = new Node(Thread.currentThread(), mode);3 // Try the fast path of enq; backup to full enq on failure4 Node pred = tail;5 if (pred != null) {6 node.prev = pred;7 if (compareAndSetTail(pred, node)) {8 pred.next = node;9 return node;
10 }
11 }
12 enq(node);
13 return node;
14 }
AQS的头结点通过unpartSuccessor方法释放同步状态,并利用LockSupport的方法唤醒其后继结点,这一过程没有使用CAS。头结点将其引用修改尾其后继结点,并断开与后继结点的连接,完成释放。
2.2.2 独占式同步状态获取与释放
调用AQS的acquire(int)方法可以独占式的获取同步状态,该方法的逻辑简单来说是这样的:首先调用tryAcquire(int)方法,线程安全地获取同步状态,如果获取同步状态失败,则构造Node结点,并将其加入等待队列中。在等待队列里的线程不断地自旋,检查其前驱是否是首结点并且能否获取同步状态,如果前驱不是首结点或者获取同步状态失败,则修改结点的等待状态,并且阻塞结点保存的线程,直到首结点释放同步状态,并唤醒后继结点,让等待的线程成功获取到同步状态,这个自旋地过程是不响应中断的。
1 public final void acquire(int arg) {2 if (!tryAcquire(arg) &&3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))4 selfInterrupt();5 }6 final boolean acquireQueued(final Node node, int arg) {7 boolean failed = true;8 try {9 boolean interrupted = false;
10 for (;;) {
11 final Node p = node.predecessor();
12 //如果前驱不是首结点或者前驱是首结点但是获取同步状态失败
13 //则被阻塞
14 if (p == head && tryAcquire(arg)) {
15 setHead(node);
16 p.next = null; // help GC
17 failed = false;
18 return interrupted;
19 }
20 if (shouldParkAfterFailedAcquire(p, node) &&
21 parkAndCheckInterrupt())
22 interrupted = true;
23 }
24 } finally {
25 if (failed)
26 cancelAcquire(node);
27 }
28 }
调用AQS的release(int)方法可以释放独占式同步状态。该方法的逻辑简单如下:首先调用treRelease释放同步状态,如果释放成功,则唤醒其后继结点,并且将头结点引用修改成其后继。由于获取到同步状态只能由一个线程,故这一操作不需要CAS来保证线程安全。
1 public final boolean release(int arg) {
2 if (tryRelease(arg)) {
3 Node h = head;
4 if (h != null && h.waitStatus != 0)
5 unparkSuccessor(h);
6 return true;
7 }
8 return false;
9 }
2.2.3 共享式同步状态获取与释放
共享式访问同步状态与独占式访问最大的区别在于,共享式访问不会阻塞其他共享式访问同步状态,但是独占式只能有一个线程进入访问,其他线程无论是共享还是独占都会被阻塞。
调用AQS的acquireShared(int)方法来共享式获取同步状态,首先调用tryAcquireShared(int)方法尝试获取共享同步状态,当该方法返回值大于等于0表示获取成功,小于0获取失败,则调用doAcquireShared(int)方法。doAcquireShared方法简单来说,就是构造Node结点,加入到同步队列尾部,然后进入自旋过程。在自选过程里,当前驱是首结点时,再次尝试获取同步状态,获取成功则退出自旋,否则被阻塞。
1 public final void acquireShared(int arg) {2 if (tryAcquireShared(arg) < 0)3 doAcquireShared(arg);4 }5 private void doAcquireShared(int arg) {6 //构造Node结点,构造状态为共享7 final Node node = addWaiter(Node.SHARED);8 boolean failed = true;9 try {
10 boolean interrupted = false;
11 //自旋尝试获取共享式同步状态
12 for (;;) {
13 final Node p = node.predecessor();
14 //只有当前驱是首结点时才会尝试获取同步状态
15 if (p == head) {
16 int r = tryAcquireShared(arg);
17 if (r >= 0) {
18 setHeadAndPropagate(node, r);
19 p.next = null; // help GC
20 if (interrupted)
21 selfInterrupt();
22 failed = false;
23 return;
24 }
25 }
26 //前驱不是首结点或者获取同步状态失败,被阻塞,直到首结点释放同步状态将其唤醒
27 if (shouldParkAfterFailedAcquire(p, node) &&
28 parkAndCheckInterrupt())
29 interrupted = true;
30 }
31 } finally {
32 if (failed)
33 cancelAcquire(node);
34 }
35 }[
调用AQS的releaseShared(int)方法释放共享式同步状态,该方法首先调用tryReleaseShared(int),尝试释放同步状态,如果释放成功,则调用doReleaseShared()方法,唤醒后续处于等待的结点,这过程是采用CAS来保证线程安全,因为共享式同步状态往往有多个线程同时持有同步状态。
2.2.4 等待队列
在总结AQS等待队列实现之前,首先要总结一下Condition接口相关的知识。
任意一个Java对象都有wait,notify,notifyAll方法,利用这些方法可以在线程不满足某些执行条件时进入等待状态,直到其他线程将其唤醒。Java对象自带的这些监视器方法配合synchronized锁可以实现等待通知范式,但是相比之下,利用Condition接口提供的方法也能做到一样的功能,并且更加灵活。且Condition接口与对象监视器方法的不同点有:Condition支持中断屏蔽等待,特定时间等待,以及最重要的,对象监视器只有一个等待队列,但是利用Condition可以支持多个条件,多个等待队列。
Condition的使用也非常简单,下面是一个简单的示例:
1 Lock lock = new ReentrantLock();2 Condition c1 = lock.newCondition();3 4 public void sample() throw InterruptedException{5 //获取锁6 lock.lock();7 try{8 //只有获取锁成功才能调用条件对象的相关方法,调用之后,线程释放锁,并被构造成Node结点9 //进入Condition的等待队列
10 condition.await();
11 }finally{
12 lock.unlock();
13 }
14 }[
获取到锁的线程调用await方法会释放锁,然后进入等待队列,直到其他线程调用signal方法将其唤醒。Condition对象的创建依赖于Lock对象。
ConditionObject是AQS的内部类,实现了Condition接口。ConditionObject对象维护等待队列的头结点和尾结点。等待队列的结点类型与同步队列结点一样,都是Node。AQS里可以被多个ConditionObject依赖,故相比于synchronized的对象监视器只能有一个等待队列,基于AQS的同步组件可以有多个等待队列。
当获取到锁的线程调用await方法时将会释放锁并进入等待状态,具体过程简单来说如下:首先检查线程中断状态,如果被中断抛出中断异常。之后调用addConditionWaiter()方法将线程引用以及等待状态构造成Node结点,并释放锁,这部分过程没有CAS来确保线程安全,因为这时候线程还持有锁。最后进入自旋过程,不断检查引用了自己的结点是否被加入到同步队列中参与同步状态的获取,如果没有被加入同步队列,则被阻塞。最后当发现自己被加入到同步队列时,退出自旋过程,调用aquiredQueued方法自旋获取同步状态。
1 public final void await() throws InterruptedException {2 if (Thread.interrupted())3 throw new InterruptedException();4 //将线程引用以及等待状态构造成新的Node结点,添加到等待队列尾部,这个过程由锁确保线程安全5 Node node = addConditionWaiter();6 //释放锁,并唤醒同步队列的后继结点7 int savedState = fullyRelease(node);8 int interruptMode = 0;9 //自旋检查自己是否被加入到同步队列,没有则被阻塞
10 while (!isOnSyncQueue(node)) {
11 LockSupport.park(this);
12 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
13 break;
14 }
15 //当发现自己被加入到同步队列,退出自旋,并参与到锁的获取
16 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
17 interruptMode = REINTERRUPT;
18 if (node.nextWaiter != null) // clean up if cancelled
19 unlinkCancelledWaiters();
20 if (interruptMode != 0)
21 reportInterruptAfterWait(interruptMode);
22 }[](javascript:void(0);
当调用ConditionObject对象的signal方法时,过程简单描述如下:首先检查并确保调用siganl方法的线程是获取锁的线程。之后调用doSignal方法,循环尝试修改等待队列头结点指向其后继,并断开原本首结点与其后继的指针,再进入自旋加CAS过程,尝试将其从等待队列转移到同步队列尾部,这个过程没有构造新的Node。当被加入到同步队列成功时,当前线程唤醒刚被插入到同步队列尾部的结点的线程,被唤醒的线程退出wait方法里的检查是否在同步队列循环,参与到自旋获取锁的过程。
1 public final void signal() {2 if (!isHeldExclusively())3 throw new IllegalMonitorStateException();4 Node first = firstWaiter;5 if (first != null)6 doSignal(first);7 }8 private void doSignal(Node first) {9 do {
10 if ( (firstWaiter = first.nextWaiter) == null)
11 lastWaiter = null;
12 first.nextWaiter = null;
13 } while (!transferForSignal(first) &&
14 (first = firstWaiter) != null);
15 }

以上就是Android技术中的JUC锁与AQS技术;这些东西可以帮助你在 APP启动优化 上面更好的铺垫;有关更多Android技术或者性能优化方面的学习;大家可以点击《Android核心技术类目》查看一些技术资料;里面内容概括了Android大部分的高工进阶知识点。
最后
AQS究竟是做什么的?
你该如何保证多个线程访问该对象时,正确地进行阻塞等待,正确地被唤醒?
关于这个问题,java的设计者认为应该是一套通用的机制,因此将一套线程阻塞等待以及被唤醒时锁分配的机制称之为AQS
全称 AbstractQuenedSynchronizer
中文名即抽象的队列式同步器。基于AQS,实现了例如ReentenLock之类的经典JUC类。
相关文章:
JUC锁与AQS技术【我的Android开发技术】
JUC锁与AQS技术【我的Android开发技术】 AQS原理 AQS就是一个同步器,要做的事情就相当于一个锁,所以就会有两个动作:一个是获取,一个是释放。获取释放的时候该有一个东西来记住他是被用还是没被用,这个东西就是一个状…...
【问题代码】顺序点的深入理解(汇编剖析+手画图解)
这好像是一个哲学问题。 目录 前言 一、顺序点是什么? 二、发生有关顺序点的问题代码 vs中: gcc中: 三、细读汇编 1.vs汇编如下(示例): 2.gcc汇编如下(示例): 四…...
BinaryAI全新代码匹配模型BAI-2.0上线,“大模型”时代的安全实践
导语BinaryAI(https://www.binaryai.net)科恩实验室在2021年8月首次发布二进制安全智能分析平台—BinaryAI,BinaryAI可精准高效识别二进制文件的第三方组件及其版本号,旨在推动SCA(Software Composition Analysis&…...
nvidia设置wifi和接口
tx-nx设置wifi和接口前言基础知识点1.创建和删除一个wifi连接2. 启动连接和关闭连接代码和调试1. 代码展示2. 调试写到最后前言 针对嵌入式开发,有时候通过QT或PAD跨网络对设备设置WIFI,在此记录下,方便后续的查阅。 基础知识点 1.创建和删…...
PostgreSQL 变化数据捕捉(CDC)
PostgreSQL 变化数据捕捉(CDC)基于CDC(变更数据捕捉)的增量数据集成总体步骤:1.捕获源数据库中的更改数据2.将变更的数据转换为您的消费者可以接受的格式3.将数据发布到消费者或目标数据库PostgreSQL支持触发器&#x…...
Spring 事务【隔离级别与传播机制】
Spring 事务【隔离级别与传播机制】🍎一.事务隔离级别🍒1.1 事务特性回顾🍒1.2 事务的隔离级别(5种)🍒1.3 事务隔离级别的设置🍎二.Spring 事务传播机制🍒2.1 Spring 事务传播机制的作用🍒2.2 事…...
HTTP和HTTPS协议
HTTP协议 HTTP协议是一种应用层的协议,全称为超文本传输协议。 URL URL值统一资源定位标志,也就是俗称的网址。 协议方案名 http://表示的就是协议方案名,常用的协议有HTTP协议、HTTPS协议、FTP协议等。HTTPS协议是以HTTP协议为基础&#…...
day3——有关java运算符的笔记
今天主要学习的内容有java的运算符 赋值运算符算数运算符关系运算符逻辑运算符位运算符(专门写一篇笔记)条件运算符运算符的优先级流程控制 赋值运算符 赋值运算符()主要用于给变量赋值,可以跟算数运算符相结合&…...
Git多人协同远程开发
1. 李四(项目负责人)操作步骤 在github中创建远程版本库testgit将基础代码上传⾄testgit远程库远程库中基于main分⽀创建dev分⽀将 githubleaflife/testgit 共享给组员李四继续在基础代码上添加⾃⼰负责的模块内容 2. 张三、王五(组员&…...
Chapter4:机器人仿真
ROS1{\rm ROS1}ROS1的基础及应用,基于古月的课,各位可以去看,基于hawkbot{\rm hawkbot}hawkbot机器人进行实际操作。 ROS{\rm ROS}ROS版本:ROS1{\rm ROS1}ROS1的Melodic{\rm Melodic}Melodic;实际机器人:Ha…...
python(14)--集合
前言 本篇文章学习的是 python 中集合的基础知识。 集合元素的内容是不可变的,常见的元素有整数、浮点数、字符串、元组等。至于可变内容列表、字典、集合等不可以是集合元素。虽然集合不可以是集合的元素,但是集合本身是可变的,可以去增加或…...
【Spark分布式内存计算框架——Spark Core】4. RDD函数(中)Transformation函数、Action函数
3.2 Transformation函数 在Spark中Transformation操作表示将一个RDD通过一系列操作变为另一个RDD的过程,这个操作可能是简单的加减操作,也可能是某个函数或某一系列函数。值得注意的是Transformation操作并不会触发真正的计算,只会建立RDD间…...
Mysql 数据类型
1、数值数据类型 1.1 整数类型(精确值) INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT MySQL支持SQL标准的整数类型INTEGER (或INT)和SMALLINT。作为标准的扩展,MySQL还支持整数类型TINYINT、MEDIUMINT和BIGINT。下表显示了每种整数类型所需的存储和范围。…...
运行Whisper笔记(1)
最近chatGPT很火,就去逛了一下openai的github项目。发现了这个项目。 这个项目可以识别视频中的音频,转换出字幕。 带着一颗好奇的心就尝试自己去部署玩一玩 跟着这篇文章一步步来进行安装,并且跟着这篇文章解决途中遇到的问题。 途中还会遇…...
2023年最强大的12款数据可视化工具,值得收藏
做数据分析也有年头了,好的坏的工具都用过,推荐几个觉得很好用的,避坑必看! PS:一般比较成熟的公司里,数据分析工具不只是满足业务分析和报表制作,像我现在给我们公司选型BI工具,是做…...
LeetCode刷题系列 -- 523. 连续的子数组和
给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:子数组大小 至少为 2 ,且子数组元素总和为 k 的倍数。如果存在,返回 true ;否则,返回 false 。如果存…...
LeetCode刷题系列 -- 525. 连续数组
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。示例 1:输入: nums [0,1]输出: 2说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。示例 2:输入: nums [0,1,0]输出: 2说明: [0, 1] (或 [1, 0]) 是具有相同数…...
JavaEE15-Spring Boot统一功能处理
目录 1.统一用户登录权限效验 1.1.最初用户登录验证 1.2.Spring AOP用户统一登录验证的问题 1.3.Spring拦截器 1.3.1.创建自定义拦截器,实现 HandlerInterceptor 接口并重写 preHandle(执行具体方法之前的预处理)方法 1.3.2.将自定义拦…...
centos7.6 设置防火墙
1、查看系统版本 cat /etc/redhat-release2、查看防火墙运行状态 systemctl status firewalld这里可以看到当前是未运行状态(inactive)。 3、关闭开机自启动防火墙 systemctl disable firewalld.service4、启动防火墙并查看状态,系统默认 22 端口是开启的。 sy…...
在线支付系列【22】微信支付实战篇之集成服务商API
有道无术,术尚可求,有术无道,止于术。 文章目录前言1. 环境搭建2. 特约商户进件3. 统一下单总结前言 在上篇文档中,我们做好了接入前准备工作,接下来使用开源框架集成服务商相关API。 一个简单的支付系统完成支付流程…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
