9.ReentrantLock

235 阅读3分钟

一、什么是ReentrantLock?

概念:ReentrantLock翻译为可重入锁,指的是一个线程能够对临界区共享资源进行重复加锁,即当前线程获取该锁后再次获取该锁不会被阻塞。 作用:确保线程安全,保证同一时刻只有一个线程可以执行临界区代码 比较: 为了大家更好的掌握 ReentrantLock 源码,这里列出两种锁之间的区别

SynchronizedReentrantLock
锁实现机制对象头监视器模式依赖 AQS
灵活性不灵活支持响应中断、超时、尝试获取锁
释放锁形式自动释放锁显示调用 unlock()
支持锁类型非公平锁公平锁 & 非公平锁
条件队列单条件队列多个条件队列
是否支持可重入支持支持

二、ReentrantLock介绍

2.1 可重入锁

概念:如果已经获取锁的线程是当前线程的话则直接再次获取成功 原理:由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。 代码

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
	if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
	//2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
		// 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
  • 判断该锁是否被其它线程所占有
  • 如果已被占有,检查占有线程是否是当前线程,如果是计数器+1,返回true获取成功
  • 否则返回false

需要注意的是,重入锁的释放必须等同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,则该锁未被完全释放返回false,只有被释放n次才算成功释放返回true。

2.1 公平锁(FairSync)

概念: 公平锁是指多个线程按照申请锁的顺序获取锁,线程直接进入队列中排队,队列中第一个线程才能获取锁

代码: 传入一个boolean值,true为公平锁,false为非公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
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;
  }
}
  • 获取当前线程,获取state状态,如果state=0(未被其它线程占用)
  • hasQueuedPredecessors判断当前节点在同步队列中是否有前驱节点,如果有前驱节点说明有线程比当前线程更早请求资源,根据公平性,则当前线程请求资源失败
  • 如果没有前驱节点继续,计数器+1,返回true,线程获取到锁

公平锁每次都是从同步队列中第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。

缺点: 公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

2.2 非公平锁(NonfairSync)

概念: 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁

代码:传入一个boolean值,true为公平锁,false为非公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

获取锁(nonfairTryAcquire)

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
	if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
	//2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
		// 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
  • 判断该锁是否被其他线程占有
  • 若被占有,检查占有线程是否为当前请求线程(重入性判断)
  • 如果是当前线程,同步状态,计数器+1,返回true

释放锁(tryRelease)

protected final boolean tryRelease(int releases) {
	//1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
		//2. 只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
	// 3. 锁未被完全释放,返回false
    setState(c);
    return free;
}

重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。

缺点

  • 非公平锁的优点是可以减少唤起线程的开销,整体吞吐率高,因为线程有几率不阻塞直接获取锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

参考: