ReentrantLock的使用以及底层源码
一、使用ReentrangLock实现多线程同步
例子:模拟妻子和丈夫去取钱,银行卡余额为100元,两个人同时取100元,那么最终余额应该是0元,有一个人取不出钱。
package com.cy.test.lock; public class Account implements Runnable { private int money = 100; //账户上默认有100元 //取钱 public void draw() { int balance = money; //当前余额 if (balance > 0) { for(int i=0; i<10000; i++){ } money = money - 100; //取走100元 System.out.println("当前余额为" + balance +"元," + Thread.currentThread().getName() + "取走100元,还剩" + money + "元"); } } @Override public void run(){ draw(); } }
妻子和丈夫分别来取钱,那么如果其中一人取走100元,另一个应该就没法取钱了,因为余额为0了。
package com.cy.test.lock; public class TestReentrantLock { public static void main(String[] args) { Account account = new Account(); Thread wife = new Thread(account, "wife"); Thread husband = new Thread(account, "husband"); //妻子取钱 wife.start(); //丈夫取钱 husband.start(); } }
console打印:
当前余额为100元,wife取走100元,还剩-100元
当前余额为100元,husband取走100元,还剩-100元
运行上面main方法,查看结果,很明显,出问题了,余额变成-100了。
这就是多线程造成的问题。
下面使用ReentrantLock来解决
package com.cy.test.lock; import java.util.concurrent.locks.ReentrantLock; public class Account implements Runnable { private int money = 100; //账户上默认有100元 private ReentrantLock lock = new ReentrantLock(); //取钱 public void draw() { try{ lock.lock(); int balance = money; //当前余额 if (balance > 0) { for(int i=0; i<10000; i++){ } money = money - 100; //取走100元 System.out.println("当前余额为" + balance +"元," + Thread.currentThread().getName() + "取走100元,还剩" + money + "元"); } }finally { lock.unlock(); } } @Override public void run(){ draw(); } }
再次运行main方法结果正确。 (当前余额为100元,wife取走100元,还剩0元)
二、ReentrantLock底层源码的实现
当调用new ReentrantLock().lock()时,调用的是sync.lock(),再是非公平NonfairSync里面的lock()方法,再是acquire(1),调用的是父类AbstractQueuedSynchronizer里面的tryAcquire(arg)方法,再是NonfairSync的tryAcquire(int acquires)方法,最终调用的是:父类Sync的nonfairTryAcquire(int acquires)
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;
}
这个是核心方法。其实AbstractQueuedSynchronizer父类中有个private volatile int state;变量,是volatile修饰的,线程之间可见,state=0时,表示线程未获取到锁。state=1时,表示线程获取到锁了。
如果state=0,执行cas方法:compareAndSetState(0, acquires),如果成功,则设置获取到锁的线程为当前线程setExclusiveOwnerThread(current),返回true。
如果state !=0,锁已被获取到,且来获取锁的线程是当前占有锁的线程,current == getExclusiveOwnerThread(),则可以继续获取到锁,表示是可重入的,设置state的值 + 1,返回true。
如果NonfairSync的tryAcquire没有获取到锁,则执行父类AbstractQueuedSynchronizer的acquireQueued(addWaiter(Node.EXCLUSIVE), arg),进入等待队列,等待锁的释放。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
父类AbstractQueuedSynchronizer中维护着一个链表,Node节点,有Node prev和Node next,因此它是双向链表,Node节点中保存着thread。就是用这个链表来实现等待队列的。aqs最核心的点在于怎么等待队列中的线程怎么入队,怎么出队的问题,原来入队出队需要加锁的,这里的入队出队用的都是cas,用cas方法往tail上加。比如多个线程都没有获取到锁,都要入队,如果队列不加锁的话,可能造成一个tail上有多个线程,那就乱了。想到的方法是给这个队列加锁,几个线程入队时拿到锁再加到tail,挨个的加进去。现在AQS实现的都是cas的方法入队出队,没有锁。

--
浙公网安备 33010602011771号