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 }[![复制代码](https://common.cnblogs.com/images/copycode.gif)
调用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 }[![复制代码](https://common.cnblogs.com/images/copycode.gif)
获取到锁的线程调用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 }[![复制代码](https://common.cnblogs.com/images/copycode.gif)](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。 一个简单的支付系统完成支付流程…...
3.2 埃尔米特转置
定义 对于复矩阵,转置又不一样,常见的操作是共轭转置,也叫埃尔米特转置Hermitian transpose。埃尔米特转置就是对矩阵先共轭,再转置,一般来说用三种符号表示埃尔米特转置: 第一种符号是AHA^HAH,…...
Python爬虫之Scrapy框架系列(13)——实战ZH小说爬取数据入MySql数据库
目录:1 数据持久化存储,写入Mysql数据库①定义结构化字段:②重新编写爬虫文件:③编写管道文件:④辅助配置(修改settings.py文件):⑤navicat创库建表:⑥ 效果如下…...
MySQL篇02-三大范式,多表查询
数据入库时,由于数据设计不合理,会存在数据重复、更新插入异常等情况, 故数据库中表的设计遵循的设计规范:三大范式1.第一范式(1NF)要求数据库的每一列都是不可分割的原子数据项,即原子性。强调的是列的原子性,即数据库中每一列的…...
vue-cli3创建Vue项目
文章目录前言一、使用vue-cli3创建项目1.检查当前vue的版本2.下载并安装Vue-cli33.使用命令行创建项目二、关于配置前言 本文讲解了如何使用vue-cli3创建属于自己的Vue项目,如果本文对你有所帮助请三连支持博主,你的支持是我更新的动力。 下面案例可供…...
Linux perf probe 的使用(三)
文章目录前言一、Dynamic Tracing二、kprobes2.1 perf kprobe 的使用2.2 kprobe Arguments3.3 tcp_sendmsg()3.3.1 Kernel: tcp_sendmsg()3.3.2 Kernel: tcp_sendmsg() with size3.3.2 Kernel: tcp_sendmsg() line number and local variable三、uprobes的使用3.1 perf uprobe …...
python GUI编程 多窗口跳转
# 多窗口跳转例子from tkinter import *def main(): # 主窗体def goto(num):root.destroy() # 关闭主窗体if num 1:one() # 进入第1个窗体elif num 2:two() # 进入第2个窗体root Tk()root.geometry(300x150600200)root.title(登录窗口)but1 Button(root, text"进入…...
nuxt 学习笔记
这里写目录标题路由跳转NuxtLinkquery参数params参数嵌套路由tab切换效果layouts 文件夹强制约定放置所有布局文件,并以插槽的形式作用在页面中1.在app.vue里面2.component 组件使用Vue < component :is"">Vuex生命周期数据请求useFetchuseAsyncDat…...
Python编程自动化办公案例(1)
作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.使用库讲解 1.xlrd 2.xlwt 二.主要案例 1.批量合并 模板如下…...
一站式 Elasticsearch 集群指标监控与运维管控平台
上篇文章写了一下消息运维管理平台,今天带来的是ES的监控和运维平台。目前初创企业,不像大型互联网公司,可以重复的造轮子。前期还是快速迭代试错阶段,方便拿到市场反馈,及时调整自己的战略和产品方向。让自己活下去&a…...
C# 调用Python
一、简介 IronPython 是一种在 NET 和 Mono 上实现的 Python 语言,由 Jim Hugunin(同时也是 Jython 创造者)所创造。 Python是一种跨平台的计算机程序设计语言。 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python是…...
贵阳网站建设包首页/百度爱采购客服电话
系列文章目录 第二章计算机网络网络应用之Socket编程应用-应用编程接口(API) Socket编程-应用编程接口(API)系列文章目录一、Socket编程-应用编程接口(API)1.网络程序设计接口2.应用编程接口(AP…...
重庆网站空间键词排名/梅州seo
文章原创首发于微信公众号:邓行长(ID:Dhangzhang),未经授权禁止转载一、你可能会懂银行柜台工作的苦银行的普通柜员工资也就4000-6000之间,拿的工资不算高。虽然个别支行效益好,绩效发的多,但是也不至于太高…...
wordpress健康主题/站长统计app最新版本2023
文章链接 转载于:https://www.cnblogs.com/luoganttcc/p/10525234.html...
wordpress会员文章/google关键词工具
云计算带来的是IT产业的转型和升级。不仅各个微观经济实体成为了云计算产业链中的参与者,各国政府也同样重视这一产业的重要变革。 毕竟,就如同制造业的变革导致了全球范围的重新分工,云计算的出现也将引发IT产业在世界范围内的再分工。 …...
网络营销就是seo/seo检测
关注微信公众号creo456 解锁更多精彩内容有时我们在画图需要测量一些尺寸,有的新手通过进入草绘环境,使用尺寸标注获得想要的尺寸,这样会降低绘图效率,那么如何快速测量想要的尺寸呢?以下图为例。方法:1.点…...
php实现网站消息推送/宁波好的seo外包公司
孙广东 2015.6.14 在UNity5.x之前的版本, 我们用文本编辑器如 sublime text 打开.unity .prefab时是可以看到 yaml格式的文本内容, 但是在 UNity5.x 当在察看.unity .prefab文件时已经变成二进制的文件了。【对不起啊,这里我要纠正一下&…...