java acquire_一步步透彻理解Lock的Acquire和Release原理源码

Java中已知的锁有两种,一种是synchronized,另一种是Lock;这两种的基本原理在之前的文章中已经做了分析:

这次我们从最常用的Lock也就是ReentrantLock的Acquire和Release锁的过程入手,一步一步的跟源代码,探究获取锁和释放锁的步骤。

获取锁

ReentrantLock lock=new ReentrantLock();

lock.lock();

1.lock()

上面代码是我们常用的获取锁的方式:调用ReentrantLock 的lock()方法,默认情况下ReentrantLock是一个非公平锁,类名NonfairSync,属于ReentrantLock的内部类,我们来看源码:

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;

/**

* Performs lock. Try immediate barge, backing up to normal

* acquire on failure.

*/

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

这里的lock方法先去通过CAS操作将state的值从0变为1,(注:ReentrantLock用state表示“持有锁的线程已经重复获取该锁的次数”。当state等于0时,表示当前没有线程持有锁),如果成功,就设置ExclusiveOwnerThread的值为当前线程(Exclusive是独占的意思,ReentrantLock用exclusiveOwnerThread表示“持有锁的线程”)。

如果设置失败,说明state>0已经有线程持有了锁,此时执行acquire(1)再请求一次锁。

2.acquire()

/**

* Acquires in exclusive mode, ignoring interrupts. Implemented

* by invoking at least once {@link #tryAcquire},

* returning on success. Otherwise the thread is queued, possibly

* repeatedly blocking and unblocking, invoking {@link

* #tryAcquire} until success. This method can be used

* to implement method {@link Lock#lock}.

*

* @param arg the acquire argument. This value is conveyed to

* {@link #tryAcquire} but is otherwise uninterpreted and

* can represent anything you like.

*/

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

这里我们注意一下acquire方法上面的注释,已经说得很清楚了,这里我大概说下我的理解:

请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞--唤醒两种状态切换中,直到tryAcquire成功。

我们对里面的tryAcquire(),、addWaiter()、acquireQueued()挨个分析。

在这个方法里先执行tryAcquire(arg):

3.tryAcquire()

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) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

先判断state是否为0,如果为0就执行上面提到的lock方法的前半部分,通过CAS操作将state的值从0变为1,否则判断当前线程是否为exclusiveOwnerThread,然后把state++,也就是重入锁的体现,我们注意前半部分是通过CAS来保证同步,后半部分并没有同步的体现,原因是:

后半部分是线程重入,再次获得锁时才触发的操作,此时当前线程拥有锁,所以对ReentrantLock的属性操作是无需加锁的。

如果tryAcquire()获取失败,则要执行addWaiter()向等待队列中添加一个独占模式的节点

4.addWaiter()

/**

* Creates and enqueues node for current thread and given mode.

*

* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared

* @return the new node

*/

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;

}

这个方法的注释:创建一个入队node为当前线程,Node.EXCLUSIVE 是独占锁, Node.SHARED 是共享锁。

先找到等待队列的tail节点pred,如果pred!=null,就把当前线程添加到pred后面进入等待队列,如果不存在tail节点执行enq()

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;

}

}

}

}

这里进行了循环,如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在,就把当前线程作为head结点。

插入节点后,调用acquireQueued()进行阻塞

5.acquireQueued()

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);

}

}

先获取当前节点的前一节点p,如果p是head的话就再进行一次tryAcquire(arg)操作,如果成功就返回,否则就执行shouldParkAfterFailedAcquire、parkAndCheckInterrupt来达到阻塞效果;

6.shouldParkAfterFailedAcquire()

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;

}

addWaiter()构造的新节点,waitStatus的默认值是0。此时,进入最后一个if判断,CAS设置pred.waitStatus为SIGNAL==-1。最后返回false。

回到第五步acquireQueued()中后,由于shouldParkAfterFailedAcquire()返回false,会继续进行循环。假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire()。上一轮循环中,已经将pred.waitStatus设置为SIGNAL==-1,则这次会进入第一个判断条件,直接返回true,表示应该阻塞。

7.parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {

LockSupport.park(this);

return Thread.interrupted();

}

很显然,一旦shouldParkAfterFailedAcquire返回true也就是应该阻塞,就会执行parkAndCheckInterrupt()来达到阻塞效果,此时线程阻塞在这里,需要其它线程来唤醒,唤醒后就会再次循环第5步acquireQueued里的请求逻辑。

我们回到第6步,看一个留下的逻辑片段

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;

}

什么时候会遇到ws > 0的case呢?当pred所维护的获取请求被取消时(也就是node的waitStatus 值为CANCELLED),这时就会循环移除所有被取消的前继节点pred,直到找到未被取消的pred。移除所有被取消的前继节点后,直接返回false。

8.cancelAcquire()

到这里我们回到第5步可以看到主体逻辑基本走完了,在该方法的finally里有一个cancelAcquire()方法

private void cancelAcquire(Node node) {

// Ignore if node doesn't exist

if (node == null)

return;

node.thread = null;

// Skip cancelled predecessors

Node pred = node.prev;

while (pred.waitStatus > 0)

node.prev = pred = pred.prev;

// predNext is the apparent node to unsplice. CASes below will

// fail if not, in which case, we lost race vs another cancel

// or signal, so no further action is necessary.

Node predNext = pred.next;

// Can use unconditional write instead of CAS here.

// After this atomic step, other Nodes can skip past us.

// Before, we are free of interference from other threads.

node.waitStatus = Node.CANCELLED;

// If we are the tail, remove ourselves.

if (node == tail && compareAndSetTail(node, pred)) {

compareAndSetNext(pred, predNext, null);

} else {

// If successor needs signal, try to set pred's next-link

// so it will get one. Otherwise wake it up to propagate.

int 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 {

unparkSuccessor(node);

}

node.next = node; // help GC

}

}

也就是在第5步的执行过程中,如果出现异常或者出现中断,就会执行finally的取消线程的请求操作,核心代码是node.waitStatus = Node.CANCELLED;将线程的状态改为CANCELLED。

释放锁

1.release()

/**

* Releases in exclusive mode. Implemented by unblocking one or

* more threads if {@link #tryRelease} returns true.

* This method can be used to implement method {@link Lock#unlock}.

*

* @param arg the release argument. This value is conveyed to

* {@link #tryRelease} but is otherwise uninterpreted and

* can represent anything you like.

* @return the value returned from {@link #tryRelease}

*/

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

我们还是通过方法上面的注释来理解一下:

释放独占锁,如果tryRelease成功返回true的话就会解开阻塞等待的线程

显然,tryRelease方法来释放锁,如果释放成功,先判断head节点是否有效,最后unparkSuccessor启动后续等待的线程。

2.tryRelease()

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;

}

先获取state减去释放的一次,然后判断当前线程是否和持有锁线程一致,如果不一致,抛出异常,继续判断state的值,只有当值为0时,free标志才置为true,否则说明是重入锁,需要多次释放直到state为0。

3.unparkSuccessor()

private void unparkSuccessor(Node node) {

/*

* If status is negative (i.e., possibly needing signal) try

* to clear in anticipation of signalling. It is OK if this

* fails or if status is changed by waiting thread.

*/

int ws = node.waitStatus;

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

/*

* Thread to unpark is held in successor, which is normally

* just the next node. But if cancelled or apparently null,

* traverse backwards from tail to find the actual

* non-cancelled successor.

*/

Node s = node.next;

if (s == null || s.waitStatus > 0) {

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);

}

这个方法名:启动后续线程,先拿到head节点的waitStatus并清空,然后获取next节点,并做检查,如果next节点失效,就从等待队列的尾部进行轮询,拿到第一个有效的节点,然后通过LockSupport.unpark(s.thread);唤醒,令该线程重新进入到获取锁的第5步循环去acquire锁。

疑惑点

1.我们在获取锁的很多步骤中看到tryAcquire的操作,原因是当获取一次失败后,程序会去执行失败后的逻辑代码,但是在执行过程中有可能锁的状态也同时发生了变化(释放锁、pred节点失效等情况),这时候需要去tryAcquire一下,省去了阻塞再唤醒的成本。

2.等待队列的waitStatus属性使用很多,在这里我们先读一下源码注释:

/** waitStatus value to indicate thread has cancelled */

static final int CANCELLED = 1;

/** waitStatus value to indicate successor's thread needs unparking */

static final int SIGNAL = -1;

/** waitStatus value to indicate thread is waiting on condition */

static final int CONDITION = -2;

/**

* waitStatus value to indicate the next acquireShared should

* unconditionally propagate

*/

static final int PROPAGATE = -3;

/**

* Status field, taking on only the values:

* SIGNAL: The successor of this node is (or will soon be)

* blocked (via park), so the current node must

* unpark its successor when it releases or

* cancels. To avoid races, acquire methods must

* first indicate they need a signal,

* then retry the atomic acquire, and then,

* on failure, block.

* CANCELLED: This node is cancelled due to timeout or interrupt.

* Nodes never leave this state. In particular,

* a thread with cancelled node never again blocks.

* CONDITION: This node is currently on a condition queue.

* It will not be used as a sync queue node

* until transferred, at which time the status

* will be set to 0. (Use of this value here has

* nothing to do with the other uses of the

* field, but simplifies mechanics.)

* PROPAGATE: A releaseShared should be propagated to other

* nodes. This is set (for head node only) in

* doReleaseShared to ensure propagation

* continues, even if other operations have

* since intervened.

* 0: None of the above

*

* The values are arranged numerically to simplify use.

* Non-negative values mean that a node doesn't need to

* signal. So, most code doesn't need to check for particular

* values, just for sign.

*

* The field is initialized to 0 for normal sync nodes, and

* CONDITION for condition nodes. It is modified using CAS

* (or when possible, unconditional volatile writes).

*/

volatile int waitStatus;

SIGNAL:后续线程正在阻塞,所以当前node在释放锁时必须启动后续线程,为了避免竞争激烈,acquire 方法第一次执行需要一个信号,也就是这个启动信号。

CANCELLED:这个node失效了,因为超时或者被中断等原因。

CONDITION:这个node当前是属于条件锁

PROPAGATE:这个node是共享锁节点,他需要进行唤醒传播

在Python解释器中,全局解释器锁(Global Interpreter Lock)保证了多线程之间的安全,但它也导致了多线程中Python代码执行的并发性问题。当多个线程同时执行Python代码时,只有一个线程能够持有全局解释器锁,其他线程必须等待该线程释放锁之后才能继续执行。这可能会导致线程的长时间等待,从而影响程序的性能。 为了解决这个问题,Python提供了一些API,比如PyEval_AcquireLock()和PyEval_ReleaseLock()。这两个函数可以在多线程环境下控制全局解释器锁的获取和释放,从而实现线程安全的Python代码执行。 以下是一个使用PyEval_AcquireLock()和PyEval_ReleaseLock()的示例: ```python import threading def run_python_code(code): # 获取全局解释器锁 PyEval_AcquireLock() try: # 执行Python代码 exec(code) finally: # 释放全局解释器锁 PyEval_ReleaseLock() # 创建两个线程 t1 = threading.Thread(target=run_python_code, args=("print('Hello from thread 1!')",)) t2 = threading.Thread(target=run_python_code, args=("print('Hello from thread 2!')",)) # 启动线程 t1.start() t2.start() # 等待线程结束 t1.join() t2.join() ``` 在上面的示例中,我们创建了两个线程,并在每个线程中执行了一段Python代码。在执行代码之前,我们使用PyEval_AcquireLock()获取了全局解释器锁,在执行完代码之后使用PyEval_ReleaseLock()释放了锁。这样可以确保代码的线程安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值