`
edgar108
  • 浏览: 33428 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

AbstractQueuedSynchronizer 独占获取锁流程

阅读更多
AbstractQueuedSynchronizer 是一个同步器,不同并发工具类,通过内部类继承AbstractQueuedSynchronizer 方式,维护状态。
同步器通过模板模式,子类重写相应方法完成状态的维护。
 
同步器依赖内部的同步队列(FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及登台状态等信息构造成为一个节点(Node)并将其加入同步队列,同时阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
 
同步器提供的模板方法:
方法名称
描述
void acquire(int arg) 独占式获取同步状态,如果当前线程获取同步状态成功,则该方法返回,否则,将会进入同步队列等待,该方法会调用重写的tryAcquire(int arg)方法
void acquireInterruptibly(in arg)
与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回
boolean tryAcquireNanos(int arg,long nanos)
在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,如果获取到了返回true
void acquireShared(int arg)
共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是同一时刻可以有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg)
与acquireShared(int arg)相同,该方法响应中断
void tryAcquireSharedNanos(int arg,long nanos)
在acquireSharedInterruptibly(int arg)基础上增加了超时限制
boolean release(int arg)
独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中的第一个节点包含的线程唤醒
boolean releaseShared(int arg)
共享式的释放同步状态
Collection<Thread> getQueuedThreads()
获取等待在同步队列上的线程集合(队列头节点未算在内,因为其thread为null)
可重写的方法:    
方法名称
描述
protected boolean tryAcquire(int arg)
独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
protected boolean tryRelease(int arg)
独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected boolean tryRcquireShared(int arg)
共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg)
共享式释放同步状态
protected boolean isHeldExclusively(int arg)
当前同步器释放在独占模式下呗线程占用,一般该方法boast是否被当前线程独占
 
acquire方法:

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

acquire方法主要完成了同步状态获取,节点构造,加入同步队列以及在同步队列中自旋等待的相关工作。其主要逻辑是:首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法保证现场安全的获取同步状态,如果同步状态获取失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部,最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞队列被中断来实现。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
通过调用compareAndSetHead(Node expect,Node update)方法来确保节点能被线程安全的添加。在enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添加,在死循环中只有通过CAS将节点设置为尾节点后,当前线程才能从该方法返回,否则当前线程不断的尝试设置。可以看出,enq(final Node node)方法将并发添加节点的请求通过CAS变得“串行化”了。
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; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
节点进入同步队列后,就进入了一个自旋的过程,每个节点(线程)都在自省的观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程)。
在acquireQueued(final Node node,int arg)方法中,当前循环在“死循环”中尝试获取同步状态,而只有前驱节点是头节点才能尝试获取同步状态。当前驱节点不是头节点时,shouldParkAfterFailedAcquire检查判断前驱节点的状态, 第一次调用,将前驱节点状态设为SIGNAL,第二次 返回true,当前线程被阻塞。
以上为独占式同步状态获取流程。

 
 
 
分享到:
评论

相关推荐

    行业-59 对MySQL锁机制再深入一步,共享锁和独占锁到底是什么?l.rar

    当一个事务请求一个共享锁时,如果这行数据没有被其他事务持有独占锁,那么这个事务就能获取共享锁并读取数据。其他事务也可以同时获得共享锁,因此多个事务可以并发地读取同一行数据,但它们都不能进行写操作,防止...

    59 对MySQL锁机制再深入一步,共享锁和独占锁到底是什么?l.pdf

    在MySQL中,最基本和常见的两种锁类型是共享锁(Shared Lock)和独占锁(Exclusive Lock),也被简称为S锁和X锁。 共享锁(S锁)允许事务读取一行数据,其他事务也可以同时读取这行数据,但不能修改。也就是说,...

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的特性,还包括了其背后的AQS框架是如何工作的。 ### 一、管程...

    读-写共享独占锁源码实现-C++实现

    3. 写锁的获取必须独占,即如果有读或写锁被持有,写线程必须等待。 4. 释放锁时,需要更新状态并唤醒可能等待的线程。 在`MySRWLockManage.cpp`和`MySRWLockManage.h`文件中,我们可能看到如下实现: ```cpp ...

    Java并发系列之AbstractQueuedSynchronizer源码分析(独占模式)

    **独占模式的获取锁过程:** 1. **尝试获取锁(tryAcquire):** `acquire()`方法首先调用`tryAcquire(arg)`,尝试直接获取锁。这个方法由子类(如ReentrantLock)实现,根据特定的逻辑判断是否能够立即获得锁。...

    读-写共享独占锁源码实现-C实现

    "读-写共享独占锁"(Read-Write Shared Exclusive Locks,简称读写锁)是一种优化多线程环境下数据访问效率的机制。在读写锁中,多个线程可以同时进行读操作,而写操作则是互斥的,即同一时间只有一个线程可以进行写...

    Linux下命令独占操作锁_Linux-Command-Lock.zip

    Linux下命令独占操作锁_Linux-Command-Lock

    浅析Sql server锁,独占锁,共享锁,更新锁,乐观锁,悲观锁

    - **独占锁(Exclusive Lock)**:这种锁也被称为X锁,它提供最高级别的锁定,不允许其他用户对锁定的资源进行读或写操作。在执行插入、更新或删除操作时,SQL Server会自动使用独占锁。独占锁一直保持到事务结束才...

    非独占锁的优先级继承协议及其在Linux下的实现.pdf

    【非独占锁的优先级继承协议及其在Linux下的实现】 在实时操作系统中,同步和互斥是确保多个任务正确执行的关键机制。非独占锁(例如读锁)允许多个低优先级的任务同时访问共享资源,而不阻塞高优先级的任务。...

    ReentrantLock源码分析

    `acquire(1)`方法用于处理未能直接通过CAS获取锁的情况,其主要流程如下: 1. **尝试获取锁**:首先调用`tryAcquire(1)`尝试获取锁。 2. **加入队列**:如果`tryAcquire(1)`返回false,则将当前线程节点添加到等待...

    java并发之ASQ

    在实际使用中,例如ReentrantLock的lock()方法,实际上是调用acquire()来尝试独占式获取锁。如果获取失败,线程会被添加到队列并阻塞,直到获得锁或者被中断。这个过程可以通过查看源码或调试来深入理解。

    MySQL:锁机制.pdf

    通过Table_locks_immediate和Table_locks_waited状态变量,可以分别获取立即获取锁的次数和等待锁的次数,从而判断表锁定的争用情况。 3. 行锁 行锁是MySQL中较为细粒度的锁类型,它只对表中特定的行进行锁定。...

    易语言线程安全之原子锁与读写锁

    4. 持有锁的线程释放锁后,其他线程按照等待顺序获取锁。 在易语言中,我们可以通过调用相应的API函数或者使用易语言提供的类库来实现读写锁。使用时,线程需要先尝试获取读锁或写锁,成功后执行相关操作,最后释放...

    Java锁的种类以及区别

    非公平锁则不保证线程获取锁的顺序,它允许正在释放锁的线程在其释放后立即重新获取锁,而不必等待队列中的其他线程。这种方式通常能够提高性能,但由于它不保证公平性,因此可能导致某些线程长期得不到锁,从而出现...

    笔记-4、显式锁和AQS(1)1

    2. **超时获取**:`tryLock(long timeout, TimeUnit unit)`方法允许我们在指定的时间内尝试获取锁,超时后会自动返回。 3. **尝试获取**:`tryLock()`方法尝试立即获取锁,如果无法立即获取则立即返回false,而不会...

    清除 vss 独占工具源码

    【清除 VSS 独占工具源码】是一款专门针对Visual ...例如,可以增加自动化脚本,定期检查并自动清除独占状态,确保开发流程的顺畅。同时,这也是一个实践和学习.NET编程、版本控制策略和问题解决技巧的好机会。

    彻底理解Java中的各种锁.pdf

    实现可重入锁的关键在于记录当前持有锁的线程以及锁的计数器,每当线程获取锁时,计数器就会递增,释放锁时递减,直到计数器归零,锁才被其他线程获取。Java中的ReentrantLock和synchronized都是可重入锁的实现。 4...

    7 AQS源码分析.docx

    在独占模式下,`tryAcquire()`方法用于尝试获取锁,而在共享模式下,使用`tryAcquireShared()`方法。 为了实现同步,AQS提供了多种策略,包括自旋、yield、sleep和park。自旋是一种常见的方式,线程会不断尝试获取...

    java 读写锁代码

    - 使用`lock()`方法获取锁,`unlock()`方法释放锁。 - 可能包含`lockInterruptibly()`方法,使得锁获取过程可以响应中断。 - 可能有`readLock().lock()`和`writeLock().lock()`来分别获取读锁和写锁。 在实际...

Global site tag (gtag.js) - Google Analytics