哪个网站教人做美食/深圳专门做seo的公司
目录
- 1、ReentrantLock 入门
- 2、ReentrantLock 源码解析
- 2.1、构造方法:默认为非公平锁
- 2.2、三大内部类
- 2.2、lock():加锁【不可中断锁】
- 2.2.1、`acquire()` 方法 —— AQS【模板方法】
- 2.2.2.1 `tryAcquire()` 方法 —— AQS,由子类去实现
- 2.2.2.2. `addWaiter()` 方法 —— AQS
- 2.2.2.3 `acquireQueued()` 方法 —— AQS
- 2.2.2.3.1、`shouldParkAfterFailedAcquire()` 方法 —— AQS
- 2.2.2.3.2、 `parkAndCheckInterrupt()` 方法 —— AQS
- 2.2.2.3.3、`cancelAcquire()` 方法 —— AQS
- 2.3、`unlock()` 方法
- 2.3.1、`tryRelease()` 方法 —— `Sync`
- 2.3.2、`unparkSuccessor()` 方法 —— AQS
- 2.4、公平锁 & 非公平锁
- 2.5、`lockInterruptibly()` 方法 —— 加锁【响应中断】
- 2.5.1、`acquireInterruptibly()` 方法 —— AQS
- 2.6、`tryLock()` 方法 —— 尝试获取锁
- 2.7、`boolean tryLock(long time, TimeUnit unit)` 方法 —— 超时获取锁
- 3、ReentrantLock & synchronized
在 五:AbstractQueuedSynchronizer 文章中,我们介绍了 AQS 的基本原理。
ReentrantLock
是我们比较常用的一种锁,也是基于 AQS 实现的。所以,接下来我们就来分析一下
ReentrantLock
锁的实现
1、ReentrantLock 入门
ReentrantLock
:可重入且互斥的锁。
案例:
public class Test {private static final Lock lock = new ReentrantLock();public static void test() {// 获取锁lock.lock();try {System.out.println(Thread.currentThread().getName() + "获取到锁了");//业务代码,使用部分花费100毫秒Thread.sleep(100);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();System.out.println(Thread.currentThread().getName() + "释放了锁");}}public static void main(String[] args){Runnable task = Test3::test;new Thread(task, "thread1").start();new Thread(task, "thread2").start();}
}
运行结果如下:
thread1获取到锁了
thread1释放了锁
thread2获取到锁了
thread2释放了锁
效果和 synchronized
的一样,线程1获取到锁了,线程2需要等待线程1释放锁后才可以获取锁
2、ReentrantLock 源码解析
类图如下:
ReentrantLock
有三个内部类:
Sync
:继承AbstractQueuedSynchronizer
(AQS),同步队列器NonfairSync
:非公平锁FairSync
:公平锁
2.1、构造方法:默认为非公平锁
public ReentrantLock() {sync = new NonfairSync();
}
// 带有参数的构造方法:公平、非公平
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
2.2、三大内部类
查看三大内部类用到的方法及方法调用的关系:
abstract static class Sync extends AbstractQueuedSynchronizer {// 抽象方法:由公平锁、非公平锁 两种方式实现abstract void lock();// 用于非公平方式,尝试获取锁final boolean nonfairTryAcquire(int acquires) {//...}// 实现了 AQS 的方法protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}
}// 非公平锁
static final class NonfairSync extends Sync {final void lock() {//...}protected final boolean tryAcquire(int acquires) {//...}
}// 公平锁
static final class FairSync extends Sync {final void lock() {//...}protected final boolean tryAcquire(int acquires) {//...}
}
2.2、lock():加锁【不可中断锁】
由于默认的是非公平锁的加锁,所以我们来分析下非公平锁是如何加锁的
public void lock() {sync.lock();
}
调用 Sync#lock()
方法:是一个抽象方法,由【公平锁】、【非公平锁】子类去实现:
abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();
}
非公平锁:
static final class NonfairSync extends Sync {final void lock() {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());} else {acquire(1);}}
}
这个方法有两步:
- 使用 CAS 来获取 state 资源,如果成功设置 1,代表 state 资源获取锁成功,此时记录下当前占用 state 的线程
setExclusiveOwnerThread(Thread.currentThread());
- 如果获取锁失败,则执行
acquire(1)
方法
2.2.1、acquire()
方法 —— AQS【模板方法】
这个方法是由 AQS 提供的模板方法,AQS#acquire()
方法:
public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){selfInterrupt();}
}
acquire()
方法:先尝试获取锁,如果获取锁成功,则返回;否则,调用 acquireQueued()
方法,将线程添加到 CLH 同步等待队列中
如图:
2.2.2.1 tryAcquire()
方法 —— AQS,由子类去实现
此方法在 AQS 中是一个空方法,留个子类自己去实现。上面我们使用的是非公平锁。所以回到 NonfairSync#tryAcquire()
方法:
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
调用 Sync#nonfairTryAcquire()
方法:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 未加锁if (c == 0) {// 加锁:CAS 操作把 state 赋值为 1,exclusiveOwnerThread() 赋值为 currentThread,然后返回 trueif (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}} else if (current == getExclusiveOwnerThread()) {// 已加锁,并且是持锁线程是当前线程(可重入),计数器加1int nextc = c + acquires;if (nextc < 0) {// 溢出throw new Error("Maximum lock count exceeded"); }setState(nextc);return true;}return false;
}
tryAcquire()
方法:尝试加锁
- 先判断当前锁是否已经被释放:如果已经被释放,那么,通过 CAS 操作去加锁,如果加锁成功,则直接返回 true【非公平锁:因为队列中的线程与新线程都可以 CAS 获取锁啊,新来的线程不需要排队】
- 如果锁还未被释放,那么判断当前线程是否是持有锁的线程,那么,就将计数器加 1,并返回 true【可重入锁】。因为就是当前线程持有锁,所以可以使用 setState() 【未使用 CAS】去更新 state 值
- 否则,返回 false,加锁失败
2.2.2.2. addWaiter()
方法 —— AQS
如果尝试加锁失败【tryAcquire()
方法返回 false】, 则执行 acquireQueued()
方法,将线程添加到 CLH 同步等待队列中
private Node addWaiter(Node mode) {// 构造一个Node对象,参数是当前线程、mode 对象,mode 表示该节点的共享/排他性,值为null为排他模式,不为null则共享模式Node node = new Node(Thread.currentThread(), mode);// 拿到队列尾节点Node pred = tail;// 尝试快速方式将新 node 直接放到队尾。// 如果尾节点不为空if (pred != null) {// 先把新加入的节点的前驱节点设置为尾节点,新加入的节点会加入队列的尾部node.prev = pred;// 通过 CAS 操作把新节点设置为尾节点,传入原来的尾节点 pred 和新节点 node 做判断,保证并发安全if (compareAndSetTail(pred, node)) {// 把新节点设置为原来尾节点的后继节点pred.next = node;// 返回新节点,这个节点里封装了当前的线程return node;}}// CAS 添加到队尾(会初始化队头)enq(node);return node;
}
addWaiter()
方法:将包含有当前线程的 Node 节点【独占模式】入队,并返回这个 Node
- 如果尾结点存在,则采用 CAS 的方式将当前线程入队
- 尾结点为空则执行 enq() 方法
enq()
方法:
private Node enq(final Node node) {// 这是一个死循环,不满足一定的条件就不会跳出循环for (;;) {// 获取队列尾节点Node t = tail;// 如果为 null,其实这是个循环判断,可能下次再做判断时,就有其他线程已经往队列中添加了节点,那么tail尾节点可能就不为空了,就走else逻辑了if (t == null) {// 必须初始化队头// 新建一个Node对象,通过 CAS 设置成头节点,这个 head 其实是冗余节点if (compareAndSetHead(new Node()))tail = head;} else {// 将此 node 添加到队尾// 尾节点不为空,则把尾节点设置为新节点的前驱节点node.prev = t;// CAS操作,把新节点设置为尾节点if (compareAndSetTail(t, node)) {// CAS 成功后,则把新节点设置为原来尾节点的后继节点t.next = node;return t;}}}
}
通过死循环的方式,来保证节点的正确添加,可以发现只有当新节点被设置为尾节点时,方法才能返回,然后再配合上 CAS,节点一个一个的被加到队列中,一个一个的接着被设置为尾节点,并发的操作,串行的感觉
使用 CAS 创建 head 节点的时候只是简单调用了 new Node() 方法,并不像其他节点那样记录 thread,这是为什么?
因为 head 结点为虚结点,它只代表持有锁线程占用了 state,至于占用 state 的是哪个线程,其实是调用了上文的 setExclusiveOwnerThread(current)
,即记录在 exclusiveOwnerThread
属性里
2.2.2.3 acquireQueued()
方法 —— AQS
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {// 标记等待过程中是否被中断过boolean interrupted = false;// 自旋for (;;) {// 获取 node 的前驱节点final Node p = node.predecessor();// 如果 node 的前驱是头节点,那么便有资格去尝试获取资源(可能是上一个获取到锁的节点释放完资源唤醒自己的,当然也可能被 interrupt 了)if (p == head && tryAcquire(arg)) {// 那么把当前节点设置为头节点,同时把当前节点的前驱节点置为nullsetHead(node);// 再把前头节点p的后继节点设置为 null,这样前头节点就没有任何引用了,帮助 GC,清理前头节点p.next = null;// 成功获取资源failed = false;// 返回等待过程中是否被中断过return interrupted;}// 当 node 的前驱节点不是头节点或者获取锁失败时,判断是否需要阻塞等待,如果需要等待,那么就调用parkAndCheckInterrupt()方法阻塞等待if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {// 如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为trueinterrupted = true;}}} finally {if (failed) {// 如果等待过程中没有成功获取资源(如timeout)| 抛异常,那么取消结点在队列中的等待。cancelAcquire(node); }}
}
acquireQueued()
方法:每个节点都会进行 2 次自旋【for(;;) {代码快}
】,每次自旋,如果前驱节点是 head 节点的话,就会去尝试加锁,如果加锁失败,就会调用 shouldParkAfterFailedAcquire()
方法去掉 CANCEL
状态的 节点,并且修改前驱节点的 waitStatues
为 SINGAL
。第二次自旋时,会调用 parkAndCheckInterrupt()
方法将当前节点阻塞起来
- 如果当前节点的前驱是 head 节点,那么调用
tryAcquire()
方法尝试加锁(有可能此时持有锁的线程已经释放了锁),如果加锁成功,那么,通过 CAS 操作把当前节点设置为 head 节点 - 如果当前节点的前驱不是 head 节点 | 尝试加锁失败,则调用
shouldParkAfterFailedAcquire()
方法,判断锁是否应该停止自旋进入阻塞状态;如果需要进入阻塞状态,则调用parkAndCheckInterrupt()
方法进行阻塞
为什么要先自旋 2 次,再进行阻塞?而不是直接就阻塞呢?
马上阻塞意味着线程从运行态转为阻塞态 ,涉及到用户态向内核态的切换,而且唤醒后也要从内核态转为用户态,开销相对比较大,所以 AQS 对这种入队的线程采用的方式是让它们先自旋来竞争锁;
如果当前锁是独占锁,如果锁一直被被持有锁线程占有, 其它线程 一直自旋没太大意义,反而会占用 CPU,影响性能,所以更合适的方式是让它们自旋一两次,竞争不到锁后识趣地阻塞起来,以等待前置节点释放锁后再来唤醒它
如果锁在自旋过程中被中断了,或者自旋超时了,应该处于「取消」状态
setHead()
方法:
private void setHead(Node node) {head = node;node.thread = null;node.prev = null;
}
将 head 设置成当前结点后,要把节点的 thread, pre 设置成 null,因为之前分析过了:head 是虚节点,不保存除 waitStatus(结点状态)的其他信息,所以这里把 thread ,pre 置为空,因为占有锁的线程由 exclusiveThread 记录了,如果 head 再记录 thread 不仅多此一举,反而在释放锁的时候要多操作一遍 head 的 thread 释放
2.2.2.3.1、shouldParkAfterFailedAcquire()
方法 —— AQS
waitStatus:默认是 0,可取值为:
- CANCELLED:1。线程已被取消,这种状态节点会被忽略,并移除队列等待 GC
- SIGNAL:-1。线程被挂起,后继节点可以尝试抢占锁
- CONDITION:-2。线程正在等待某些条件
- PROPAGATE:-3。共享模式下,无条件所有等待线程尝试抢占锁
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取前驱状态int ws = pred.waitStatus;if (ws == Node.SIGNAL) {// 表示要阻塞return true;}// 如果状态大于0,表示前驱节点需要做的请求被取消了if (ws > 0) {// 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边// 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被 GC 回收!do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 如果前驱正常,那就通过 CAS 操作把前驱的状态设置成 SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}
shouldParkAfterFailedAcquire()
方法:判断锁是否应该停止自旋(并进入阻塞状态)
- 判断当前节点的前驱节点的
waitStatus
是否为SIGNAL
,如果是,则返回 true,表示当前节点停止自旋,应该进入阻塞状态; - 如果不是且前驱节点的
waitStatus
为CANCELLED
状态,那么就把当前节点放到waitStatus
为非CANCELLED
的节点后面 - 否则,将前驱节点的
waitStatus
置为SIGNAL
状态
2.2.2.3.2、 parkAndCheckInterrupt()
方法 —— AQS
private final boolean parkAndCheckInterrupt() {// 调用park()使线程进入waiting状态LockSupport.park(this);// 如果被唤醒,查看自己是不是被中断的。return Thread.interrupted();
}
shouldParkAfterFailedAcquire()
方法返回 true,代表线程可以进入阻塞中断。
为什么要判断线程是否中断过呢?
因为如果线程在阻塞期间收到了中断,唤醒(转为运行态)获取锁后(acquireQueued()
为 true)需要补一个中断
static void selfInterrupt() {Thread.currentThread().interrupt();
}
由于在整个抢锁过程中,我们都是不响应中断的。那如果在抢锁的过程中发生了中断怎么办呢?总不能假装没看见呀。AQS 的做法简单的记录有没有有发生过中断,如果返回的时候发现曾经发生过中断,则在退出 acquire()
方法之前,就调用 selfInterrupt()
方法自我中断一下,就好像将这个发生在抢锁过程中的中断“推迟”到抢锁结束以后再发生一样。
2.2.2.3.3、cancelAcquire()
方法 —— AQS
private void cancelAcquire(Node node) {// 如果节点为空,直接返回if (node == null) {return;}node.thread = null;// 跳过所有取消状态的结点Node pred = node.prev;while (pred.waitStatus > 0) {node.prev = pred = pred.prev;}Node predNext = pred.next;node.waitStatus = Node.CANCELLED;// 如果当前取消结点为尾结点,使用 CAS 则将尾结点设置为其前驱节点,如果设置成功,则尾结点的 next 指针设置为空if (node == tail && compareAndSetTail(node, pred)) {compareAndSetNext(pred, predNext, null);} else {// 如果当前节点取消了,那是不是要把当前节点的前驱节点指向当前节点的后继节点,但是我们之前也说了,要唤醒或阻塞结点,须在其前驱节点的状态为 SIGNAL 的条件才能操作,所以在设置 pre 的 next 节点时要保证 pre 结点的状态为 SIGNALint ws;if (pred != head &&((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&pred.thread != null) {Node next = node.next;if (next != null && next.waitStatus <= 0)compareAndSetNext(pred, predNext, next);} else {// 如果 pre 为 head,或者 pre 的状态设置 SIGNAL 失败,则直接唤醒后继结点去竞争锁,之前我们说过, SIGNAL 的结点取消(或释放锁)后可以唤醒后继结点unparkSuccessor(node);}node.next = node; // help GC}
}
什么时候会出现 CANCEL 状态的节点?
- 线程发生中断
- 线程获取锁超时
2.3、unlock()
方法
public void unlock() {sync.release(1);
}
调用 AQS#release()
方法,是个模板方法
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0) {unparkSuccessor(h);}return true;}return false;
}
如果锁释放成功,则唤醒同步等待队列中 head 的一个后继节点,并返回 true;否则,直接返回 false
2.3.1、tryRelease()
方法 —— Sync
protected final boolean tryRelease(int releases) {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 为 0,则返回 true,表示释放锁成功;否则,返回 false
锁释放成功后就应该唤醒 head 节点之后的节点来竞争锁
为什么释放锁的条件为啥是
h != null && h.waitStatus != 0
?
h == null
:head 节点为 null,有两种可能- 当前只有一个线程访问,且就是持有锁的线程,即:同步等待队列中没有阻塞线程
- 其它线程正在运行竞争锁,只是还未初始化 head 节点,既然其它线程正在运行,也就无需执行唤醒操作
h != null && h.waitStatus == 0
:head 的后继节点 T 正在自旋竞争锁(T 还未将它的前驱节点 head 的状态修改为SIGNAL
),无需唤醒h != null && h.waitStatus < 0
:此时 waitStatus 值可能为 SIGNAL,或 PROPAGATE,这两种情况说明后继结点阻塞需要被唤醒
2.3.2、unparkSuccessor()
方法 —— AQS
private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0) {compareAndSetWaitStatus(node, ws, 0);}// 以下操作为获取队列第一个非取消状态的结点,并将其唤醒Node s = node.next;if (s == null || s.waitStatus > 0) {// s 为空,或者其为取消状态,说明 s 是无效节点,此时需要执行 for 里的逻辑s = null;// 以下操作为从尾向前获取最后一个非取消状态的结点for (Node t = tail; t != null && t != node; t = t.prev) {if (t.waitStatus <= 0) {s = t; }}}if (s != null) {LockSupport.unpark(s.thread);}
}
unparkSuccessor()
方法:在同步等待队列中,从尾向前获取最后一个非取消状态的结点,并将其唤醒
2.4、公平锁 & 非公平锁
公平锁与非公平锁的实现区别:tryAcquire()
方法
hasQueuedPredecessors()
方法:判断当前线程前面有没有在排队的线程,有则返回 true,否则返回 false
public final boolean hasQueuedPredecessors() {Node t = tail; Node h = head;Node s;return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
区别如下:
- 公平锁:多个线程按照申请锁的顺序去获得锁。当多个线程进行访问时,如果同步等待队列中有线程等待【锁已经被某个线程持有】,那么它不会去尝试获取锁,而是直接进入队列去排队
- 优点:所有的线程都能得到资源,不会饿死在队列中
- 缺点:吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,CPU 唤醒阻塞线程的开销会很大
- 非公平锁:当多个线程进行访问时,即使锁已经被持有,且同步等待队列中已有其它线程在等待,那么,它也会先去尝试获取锁。如果能获取锁,则就加锁;否则,进入同步等待队列中,先自旋再阻塞
- 优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率会高点,CPU 也不必取唤醒所有线程,会减少唤起线程的数量
- 缺点:这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死
2.5、lockInterruptibly()
方法 —— 加锁【响应中断】
public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);
}
2.5.1、acquireInterruptibly()
方法 —— AQS
public final void acquireInterruptibly(int arg) throws InterruptedException {// 如果线程中断,则抛异常if (Thread.interrupted()) {throw new InterruptedException();}if (!tryAcquire(arg)) {doAcquireInterruptibly(arg);}
}
doAcquireInterruptibly()
方法:发生中断后,会将此节点置为 CANCEL
状态
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;failed = false;return;}// 线程被唤醒时,如果发生中断,则抛异常if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {throw new InterruptedException();} }} finally {if (failed)cancelAcquire(node);}
}
lockInterruptibly()
:发生了中断是会直接抛InterruptedException
异常,响应中断,立即停止获取锁的流程【你的线程对中断敏感】【可以用来解决死锁问题】lock()
:发生了中断之后,会继续尝试获取锁,通过返回中断标识延迟中断【你的线程对中断不敏感,当然,也要注意处理中断】
2.6、tryLock()
方法 —— 尝试获取锁
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()) {int nextc = c + acquires;if (nextc < 0) {throw new Error("Maximum lock count exceeded");}setState(nextc);return true;}return false;
}
tryLock()
方法:尝试获取锁,获取锁成功,返回 true;否则,返回 false
2.7、boolean tryLock(long time, TimeUnit unit)
方法 —— 超时获取锁
static final long spinForTimeoutThreshold = 1000L;private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {if (nanosTimeout <= 0L) {return false;}final long deadline = System.nanoTime() + nanosTimeout;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;failed = false;return true;}nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L) {return false;}// 阻塞给定的超时时间if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) {LockSupport.parkNanos(this, nanosTimeout);}// 中断抛异常if (Thread.interrupted()) {throw new InterruptedException();}}} finally {if (failed)cancelAcquire(node);}
}
3、ReentrantLock & synchronized
虽然在性能上 ReentrantLock
和 synchronized
没有什么区别,但 ReentrantLock
相比 synchronized
而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。比如:公平锁/非公平锁、尝试获取锁、超时获取锁以及中断等待锁的线程等
共同点:
- 都是独占锁、非公平锁
- 都是可重入的
不同点:
- 底层实现不同:
synchronized
是 JVM层面的锁,是Java关键字,通过 monitor 对象来完成(monitorenter 与 monitorexit),ReentrantLock 是 JDK 的 API - 锁的对象不同:
synchronzied
锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock
是根据volatile
变量state
标识锁的获得/争抢 - 实现机制不同:
synchronized
的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向内核态申请重量级锁;ReentrantLock
实现则是通过利用 CAS(CompareAndSwap)自旋机制保证线程操作的原子性和 volatile 保证数据可见性以实现锁的功能 - 释放锁方式上不同:
synchronized
不需要用户去手动释放锁,synchronized
代码执行完后系统会自动释放锁;ReentrantLock
需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()
和unlock()
方法配合try/finally
语句块来完成
相关文章:

六:ReentrantLock —— 可重入锁
目录 1、ReentrantLock 入门2、ReentrantLock 源码解析2.1、构造方法:默认为非公平锁2.2、三大内部类2.2、lock():加锁【不可中断锁】2.2.1、acquire() 方法 —— AQS【模板方法】2.2.2.1 tryAcquire() 方法 —— AQS,由子类去实现2.2.2.2. a…...

一种驱动器的功能安全架构介绍
下图提供了驱动器实现安全功能的架构 具有如下特点: 1.通用基于总线或者非总线的架构。可以实现ethercat的FSOE,profinet的profisafe,或者伺服本体安全DIO现实安全功能。 2.基于1oo2D架构,安全等级可以达到sil3。 3.高可用性。单…...

紫光展锐T610平台_4G安卓核心板方案定制开发
紫光展锐T610核心板配备Android 11操作系统,采用12nm制程工艺。该处理器CPU由2颗基于Cortex-A75架构的大核心和6颗基于Cortex-A55架构的小核心组成,最高主频为1.8GHz。GPU采用的是614.4MHz的Mali G52,可以流畅播放2400*1080分辨率视频&#x…...

C++11 设计模式4. 抽象工厂(Abstract Factory)模式
问题的提出 从前面我们已经使用了工厂方法模式 解决了一些问题。 现在 策划又提出了新的需求:对于各个怪物,在不同的场景下,怪物的面板数值会发生变化, //怪物分类:亡灵类,元素类,机械类 …...

第8周 Python面向对象编程刷题
单击题目,直接跳转到页面刷题,一周后公布答案。加入QQ群701657573,随时答疑交流。 218:类对象属性219:坐标对象相加220:计算周长221:学生分数总和222:车辆类中创建引擎类对象223&am…...
【学习心得】神经网络知识中的符号解释②
我在上篇文章中初步介绍了一些神经网络中的符号,只有统一符号及其对应的含义才能使我自己在后续的深度学习中有着一脉相承的体系。如果对我之前的文章感兴趣可以点击链接看看哦: 【学习心得】神经网络知识中的符号解释①http://t.csdnimg.cn/f6PeJ 一、…...

Igh related:Small Bug And Notes Record.
Write at the top My computer got some silly problem with the typing software that my Chinese IM does’t work again. So I’ll try to record the things happened in English. If any error,DM me plz. BUGs BUG1 Undefined symbol Identifier “CLOCK_MONOTONIC”…...

【QT入门】Qt自定义控件与样式设计之qss介绍(Qt style sheet)
往期回顾: 【QT入门】 无边框窗口设计之实现圆角窗口-CSDN博客【QT入门】 无边框窗口设计综合运用之自定义标题栏带圆角阴影的窗口-CSDN博客 【QT入门】 无边框窗口设计之综合运用,实现WPS的tab页面-CSDN博客 【QT入门】Qt自定义控件与样式设计之qss介绍…...

[ LeetCode ] 题刷刷(Python)-第49题:字母异位词分组
题目描述 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词是由重新排列源单词的所有字母得到的一个新单词。 即将含有相同字符但排列顺序不同的字符串放入同一个组中。 示例 示例 1: 输入: strs ["eat", &qu…...

冒泡排序算法实现步骤
算法实现的过程: 1. 定义问题: - 算法是用来解决某一特定计算问题的方法步骤。例如,对于排序问题,我们需要一个算法对一组无序的整数进行排序。 2. 设计算法: - 冒泡排序是一种基础的排序算法。它的设计思路是…...

js实现webp转png/jpg
网上保存的图片是webp类型的,但是我把它嵌入flac格式的音频里后导致网页中无法播放 wps要会员,真麻烦。 完整代码: <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8">…...

DVWA -File Upload-通关教程-完结
DVWA -File Upload-通关教程-完结 文章目录 DVWA -File Upload-通关教程-完结页面功能LowMediumHighImpossible 页面功能 此页面的功能为选择某个图片文件点击Upload按钮上传,上传成功后得知文件上传路径为DVWA\hackable\uploads。 Low 源码审计 这段 PHP 代码…...

中介者模式:简化对象间通信的协调者
在面向对象的软件开发中,中介者模式是一种重要的行为型设计模式,用于降低多个对象间通信的复杂性。通过提供一个中心化的对象来处理不同组件之间的交互,中介者模式使得组件间不必显式引用彼此,从而使其松散耦合、更易于维护。本文…...

【Python系列】pydantic版本问题
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

深度学习-多尺度训练的介绍与应用
一、引言 在当今快速发展的人工智能领域,多尺度训练已经成为了一种至关重要的技术,特别是在处理具有复杂结构和不同尺度特征的数据时。这种技术在许多应用中发挥着关键作用,例如图像识别、自然语言处理和视频分析等。 多尺度训练的定义 多尺…...

详解单文件组件
当你创建 Vue 单文件组件时,通常会包含三个部分:<template>、<script> 和 <style>。这三个部分分别用于定义组件的模板、逻辑和样式。让我更详细地解释一下它们的作用和用法: <template> <template> 标签用于…...

MLeaksFinder报错
1.报错:FBClassStrongLayout.mm 文件:layoutCache[currentClass] ivars; 解决:替换为layoutCache[(id)currentClass] ivars; 2.编译正常但运行时出现crash indirect_symbol_bindings[i] cur->rebinding FBRetainCycleDetector iOS15 …...

【心路历程】初次参加蓝桥杯实况
送给大家一句话: 寂静的光辉平铺的一刻,地上的每一个坎坷都被映照得灿烂。 – 史铁生 《我与地坛》 初次参加蓝桥杯有感 一点小小的震撼难评的做题过程A题 艺术与篮球问题描述解题 B 题 五子棋问题描述解题 C题 训练士兵问题描述解题 D题 团建解题 E题 …...

微信小程序全屏开屏广告
效果图 代码 <template><view><!-- 自定义头部 --><u-navbar title" " :bgColor"bgColor"><view class"u-nav-slot" slot"left"><view class"leftCon"><view class"countDown…...

记录day1
1.早上 ①协同过滤算法 基于物品基于用户分别是如何实现的 相似度的计算方式 基于用户和基于物品的区别 实时性和新物品这里: 实时性指的是用户有新行为,这样基于物品就好,因为用户新行为了,用户矩阵不会变化,用户…...

stm32GPio的开发基础
上拉输入:高电平(弱高电平,一般默认) 下拉输入:低电平 没有上拉下拉就是处于一个不确定的状态 推挽wan输出:驱动能力比较强,推挽是因为往外推 set就是1,reset就是0 XMX就是封装的…...

DataSource
目录 1、 DataSource 1.1、 * 建立数据库连接的参数对象 1.1.1、 * 数据库url 1.1.2、 * 数据库用户名 1.1.3、 * 数据库密码 1.1.4、 * 数据库驱动名称 <...

Linux防止暴力破解密码脚本
1.认识记录linux记录安全的日志 [roottestpm ~]# cd /var/log/ [roottestpm log]# ls | grep secure secure 2.该日志的内容查看 [roottestpm log]# tail -f secure #表示ssh身份验证失败 Aug 29 23:35:03 testpm sshd[111245]: pam_unix(sshd:auth): authentication fa…...

Unity 遮罩
编辑器版本 2017.2.3f1 学习Unity的三张遮罩方式 1. Mask 遮罩方式 首先,在界面上创建2个Image,一个命名Img_Mask,大小设置 400* 400, 一个命名Img_Show,大小设置500*500。 然后,给 Img_Mask添加Mask,选择Img_Mask,点击Add Com…...

jmeter实验 模拟:从CSV数据到加密请求到解密返回数据再到跨越线程组访问解密后的数据
注意,本实验所说的加密只是模拟加密解密,您需要届时写自己的加解密算法或者引用含有加密算法的相关jar包才行. 思路: 线程组1: 1.从CSV文件读取原始数据 2.将读取到的数据用BeanShell预习处理器进行加密 3.HTTP提取器使用加密后的数据发起请求 4.使用BeanShell后置处理器…...

设计模式——外观(门面)模式10
外观模式:能为系统框架或其他复杂业务流程封装提供一个简单的接口。 例如抽奖过程中 设计模式,一定要敲代码理解 调用1(抽奖系统) /*** author ggbond* date 2024年04月08日 10:34*/ public class Lottery {public String getId…...

第七周周一人工智能导论预告
第一讲 人工智能概述 1.1 简介 1.2人工智能的概念 1.3 人工智能的发展简史 1.4 人工智能研究的基本内容 第一讲 人工智能概述单元测试 第二讲 一阶谓词逻辑表示法 2.1 命题逻辑 2.2 谓词逻辑 2.3 一阶谓词逻辑知识表示法 第二讲 一阶谓词逻辑知识表示法单元测试 第…...

npm install 的不同选项:--save、--save-dev、-S、-D 的区别
Node.js 的包管理器 npm 提供了一个命令 npm install,用于安装 Node.js 项目所需的依赖包。在使用这个命令时,我们可以通过添加不同的选项来控制依赖包的安装方式。本文将详细介绍这些选项:--save、--save-dev、-S 和 -D 的区别。 1. --save…...

设计模式详解(十四)——策略模式
策略模式简介 策略模式定义 策略模式(Strategy Pattern)是一种行为型设计模式,它使能在运行时改变对象的行为。策略模式属于对象行为模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换…...

【牛客SQL快速入门】SQL基础(二)
一、高级查询 1. 计算函数 AVG AVG()为平均值函数,通过对表中行数计数并计算其列值之和,求得该列的平均值。 AVG() 可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。 Select avg(gpa) From user_profile COUNT COUNT()函数…...