在前面文章分析几个队列的时候,可以看到里面都用到了ReentrantLock这个类,使用方法都是一样
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
没错,这就是实现锁功能的一个类,在JDK1.5以后引入,实现Lock接口,拥有与synchronized相同的并发性和内存语义,可以说synchronized能做的事它都能做,并且还能比synchronized做更多的事,比如锁投票,可中断锁,条件变量等,内部包含公平锁(即先申请锁的线程先获取锁)和非公平锁(不是FIFO规则,后来的线程可以比先来的线程获取锁)的实现,并且在多线程访问共享资源时,JVM可以花更少的时间调度线程,把更多时间用于线程处理;其实就是把synchronized改进后的一种锁机制。
ReentrantLock内部封装了公平锁和非公平锁,两种锁都继承了一个内部类Sync,这个父类继承AbstractQueuedSynchronizer,AbstractQueuedSynchronizer作用主要是提供加锁、释放锁,并在内部维护一个FIFO等待队列,用于存储由于锁竞争而阻塞的线程。
源码实现
内部定义了一个变量
/** Synchronizer providing all implementation mechanics
* 同步器
* */
private final Sync sync;
然后就是三个内部类
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
* 锁的同步控制基础 有两个子类
* AbstractQueuedSynchronizer利用硬件原语指令(CAS compare-and-swap),
* 来实现轻量级多线程同步机制,并且不会引起CPU上文切换和调度,
* 同时提供内存可见性和原子化更新保证(线程安全的三要素:原子性、可见性、顺序性)
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
* 抽象方法,子类去实现
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
* 非公平锁中被tryAcquire调用
* 表示将当前线程加锁标志或可重入标识,是实现加锁功能的主要方法
*/
/// OPENJDK-9 @ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取独占锁的数量
int c = getState();
//如果独占锁数量为0,说明该锁被释放,没有线程在使用
if (c == 0) {
//利用cas将state由0改成1
if (compareAndSetState(0, acquires)) {
//将当前线程设置为该独占锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//判断当前线程是不是该独占锁所得线程
//如果是就将独占锁数量+1
int nextc = c + acquires;
if (nextc < 0) // 如果超过最大独占锁数量,抛出异常
throw new Error("Maximum lock count exceeded");
//设置新的独占锁数量
setState(nextc);
return true;
}
return false;
}
/// OPENJDK-9 @ReservedStackAccess
//每释放一次锁,就将state减1
protected final boolean tryRelease(int releases) {
//获取释放锁后独占锁的数量
int c = getState() - releases;
//若当前的线程与使用锁的线程不一致,则抛出IllegalMonitorStateException(非法监视器状态异常)
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//若是该线程最后一次持有锁,则通知AQS不再记录当前持有锁的线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置新的等待锁数量
setState(c);
return free;
}
//判断当前独占锁的线程是否等于当前监听的线程
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
//返回一个锁的判断条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
//获取当前独占锁的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取独占锁状态,如果当前线程是独占锁的线程,则返回当前标志位,否则返回0
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//表示当前是否被锁住
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
/**
* Sync object for non-fair locks
* 实现非公平锁,不保证线程按申请顺序获取锁
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
/// OPENJDK-9 @ReservedStackAccess
//尝试抢占锁
final void lock() {
//利用cas将state由0改成1,将当前线程设置为该独占锁的线程,如果失败就进入队列等待
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//重写AbstractQueuedSynchronizer的tryAcquire方法,调用Sync中的nonfairTryAcquire方法,将当前线程加锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
* 实现公平锁 即先申请的线程先获取锁
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//进入队列等待
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
/// OPENJDK-9 @ReservedStackAccess
//重写AbstractQueuedSynchronizer的tryAcquire方法
// 与父类中的nonfairTryAcquire就多加一个判断条件,判断是否有其它线程等待时间比当前线程等待时间长
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
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;
}
}
主要作用就是实现了一个公平锁和非公平锁,获取锁和释放锁的逻辑
接下来就是构建ReentrantLock实例了
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
* 实例化一个非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
* 根据条件实例化公平锁或者非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
接下来看使用方法
//获取锁,实际使用时常用的加锁方式
//如果这个所没有被另外一个线程占用就立即返回,将锁数量加1
public void lock() {
sync.lock();
}
//获取可中断的锁,如果后续线程中断(Thread#interrupt)当前线程,当前线程就被释放锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//尝试获取锁,如果锁没有被其它线程占用 ,就获取锁,返回true;否则获取失败,返回false,不会等待
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//释放锁,会走到Sync父类的tryRelease方法,这里如果调用线程与当前占用锁的线程不一样 会抛出异常
public void unlock() {
sync.release(1);
}
//判断当前锁是不是公平锁
public final boolean isFair() {
return sync instanceof FairSync;
}
其它方法比较简单就不一一介绍了,其实ReentrantLock这个类可以看成是AbstractQueuedSynchronizer这个类的使用封装。
获取非公平锁工作流程
AbstractQueuedSynchronizer在下面简称AQS
- 先构建一个ReentrantLock实例,默认使用非公平锁
- 调用ReentrantLock的 lock()获取锁,这个方法会调用NonfairSync类的lock()方法
- 首先会利用cas将state由0改成1,即改变锁的数量,如果设置成功,表明插队成功了,就调用setExclusiveOwnerThread方法将当前线程设置为该独占锁的线程;
- 如果设置失败,即被其它线程抢先了一步占有锁了,那就调用AQS类的acquire方法
- acquire方法会先调用tryAcquire方法进行判断,tryAcquire在NonfairSync被重写,然后该方法里又走到了父类Sync的nonfairTryAcquire方法中
- 该方法里会先获取锁数量,如果是0,那就说明该锁被释放,没有线程使用,再利用cas将state由0改成1,并且将当前线程设置为该独占锁线程;如果不等于0,判断当前线程是不是该独占锁的线程,如果是,就将当前的锁数量+1(这也就是可重入锁的名称的来源)
- 如果tryAcquire方法返回false,即获取锁失败,就会继续执行AQS里的acquireQueued方法和selfInterrupt()方法,这里就不深入解析这个类了,后续再出文章解析这个类,最终的结果就是将当前线程加入队尾并挂起,以后等待被唤醒去获取锁
至于获取公平锁这里就不具体分析了,大概逻辑与获取非公平锁逻辑差不多,主要少了插队部分(即少了CAS尝试将state从0设为1,进而获得锁的过程),多了需要判断当前线程是否在等待队列首部的逻辑。
公平锁和非公平锁的区别
公平给人第一感觉就是好事,是个褒义词,那为什么还要非公平锁呢。就像我们生活中一样,一个群体要维护公平规则,是需要花费很大的成本的;公平保证了锁的健壮性,但公平锁就会比非公平锁的吞吐量低,所以默认情况下应该是执行非公平锁,除非你的算法对公平的需求很大,需要严格按照线程队列顺序进行操作。其实JVM会保证所有的线程最终都会确保他们获取锁,这就足够我们使用了,这比保证绝对公平要减少很多成本。
条件变量
我们在自己开发线程同步程序时会获取对象的锁,然后用到wait、notify、notifyAll等方法在某些条件达到时进行线程调度;现在我们使用了ReentrantLock,它实现的Lock接口就有对包含wait和notify等同步的概括,Condition就是这种概括;当我们使用ReentrantLock替代 synchronized(同步代码块和同步方法)时,我们就可以使用Condition的 await 、 signal 和 signalAll 方法代替wait、notify、notifyAll方法。至于ReentrantLock和synchronized有啥联系,可以去这篇文章看看ReentrantLock和synchronized两种锁定机制的对比
使用例子
ReentrantLock putLock = new ReentrantLock();
Condition condition = putLock.newCondition();
int count;
/**
* @throws InterruptedException
*/
public void produce(String name) throws InterruptedException {
putLock.lock();
try {
while (count == 10)
condition.await();
count++;
Log.e(TAG,name+"--账户充值后是 "+count+"元");
if (count > 0) {
condition.signal();
}
}finally {
putLock.unlock();
}
}
/**
* @return
* @throws InterruptedException
*/
public void consume(String name) throws InterruptedException {
putLock.lock();
try {
while (count == 0)
condition.await();
count--;
Log.e(TAG,name+"--账户消费后还剩 "+count+"元");
if (count < 10) {
//将等候时间最长的线程(如果存在)从此状态的等待队列移至拥有锁的等待队列
condition.signal();
}
}finally {
putLock.unlock();
}
}
public class AppleProducer implements Runnable{
private String TAG = "Producer";
//生产者
private String produce;
//生产机器
private Basket basket;
public AppleProducer(String produce, Basket basket) {
this.produce = produce;
this.basket = basket;
}
@Override
public void run() {
try {
for (; ;) {
basket.produce(produce);
Thread.sleep(1000 * 2);
}
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException=" + e.getMessage());
e.printStackTrace();
} catch (IllegalStateException e) {
Log.e(TAG, "IllegalStateException=" + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
Log.e(TAG, "Exception=" + e.getMessage());
e.printStackTrace();
}
}
}
public class AppleConsumer implements Runnable {
private String TAG = "Consumer";
private String consume;
private Basket basket;
public AppleConsumer(String consume, Basket basket) {
this.consume = consume;
this.basket = basket;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000 * 2);
basket.consume(consume);
}
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException=" + e.getMessage());
e.printStackTrace();
} catch (IllegalStateException e) {
Log.e(TAG, "IllegalStateException=" + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
Log.e(TAG, "Exception=" + e.getMessage());
e.printStackTrace();
}
}
}
Basket basket = new Basket();
ExecutorService executors = Executors.newFixedThreadPool(5);
AppleProducer producer = new AppleProducer("一号生产者",basket);
AppleConsumer consumer = new AppleConsumer("一号消费者",basket);
executors.execute(producer);
executors.execute(consumer);
例子的逻辑:
- 就是一个线程不停的给count的值+1,另一个线程不停的给count值-1
- 当一个线程进行+操作的时候,先获取锁,然后判断count是否等于10,如果等于,就释放锁,进入阻塞状态;否则就进行+操作,只要count>0,就通知那些在等待给count减值的线程
- 当一个线程进行-操作的时候,先获取锁,然后判断count是否等于0,如果等于0,就释放锁,进入阻塞状态;否则进行-操作,只要count<10,就通知那些在等待给count加值的线程
上面的例子是用了一把锁去控制加和减操作,在高并发的情况下对于效率的优化我们会考虑到对数据队列的入队操作和出队操作分别用不同的锁去控制;这就好比一个出水口一个入水口,一个人要去同时关注两个水口,以免水溢出或者水干涸换成两个人分别去控制两个水口。看下面的例子
ReentrantLock putLock = new ReentrantLock();
Condition notTen = putLock.newCondition();
ReentrantLock takeLock = new ReentrantLock();
Condition notZero = takeLock.newCondition();
int count;
/**
* @throws InterruptedException
*/
public void produce(String name) throws InterruptedException {
putLock.lock();
try {
while (count == 10)
notTen.await();
count++;
Log.e(TAG,name+"--账户充值后是 "+count+"元");
if (count > 0) {
notTen.signal();
}
}finally {
putLock.unlock();
}
if (count -1 == 0) {
signalNotZero();
}
}
public void signalNotZero(){
takeLock.lock();
try {
notZero.signal();
}finally {
takeLock.unlock();
}
}
/**
* @return
* @throws InterruptedException
*/
public void consume(String name) throws InterruptedException {
takeLock.lock();
try {
while (count == 0)
notZero.await();
count--;
Log.e(TAG,name+"--账户消费后还剩 "+count+"元");
if (count < 10) {
notZero.signal();
}
}finally {
takeLock.unlock();
}
if (count + 1 == 10) {
signalNotTen();
}
}
public void signalNotTen(){
putLock.lock();
try {
notTen.signal();
}finally {
putLock.unlock();
}
}
现在的逻辑就是:
- 使用putLock锁去控制加操作,使用takeLock锁去控制减操作
- 当多个线程去进行加操作的时候,只要有一个线程把值加到了10,后面进来的线程就会进入阻塞状态;并且如果一个线程进来后count值是0然后开始加,说明进行减操作的线程已经阻塞了,那就唤醒其它进行减操作的线程
- 当多个线程进行减操作的时候,只要有一个线程把值减到了0,后面进来的线程就会进入阻塞状态;并且如果一个线程进来后count值是从10开始减的,说明进行加操作的线程已经阻塞了,那就唤醒其它进行加操作的线程