ReentrantLock(可重入锁)
ReentrantLock具有重入性,也就是说线程可以对它已经加锁的ReentrantLock再次加锁,ReentrantLock对象会持有维持一个计算器来追踪lock方法的嵌套调用,线程在每次调用lock()加锁后,必须显示调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
特性
ReentrantLock可以实现公平锁。
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。在创建ReentrantLock的时候通过传进参数true
创建公平锁,如果传入的是false
或没传参数则创建的是非公平锁。
ReentrantLock lock = new ReentrantLock(true);
ReentrantLock可响应中断
当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()
。该方法可以用来解决死锁问题。
获取锁时限时等待
ReentrantLock还给我们提供了获取锁限时等待的方法tryLock()
,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以使用该方法配合失败重试机制来更好的解决死锁问题。
结合Condition实现等待通知机制
使用synchronized结合Object上的wait和notify方法可以实现线程间的等待通知机制。ReentrantLock结合Condition接口同样可以实现这个功能。而且相比前者使用起来更清晰也更简单。
public class Account {
private final Lock mLock = new ReentrantLock();
private final Condition mCondition = mLock.newCondition();
private String mAccountNo;
/**
* 余额
*/
private double mBalance;
private boolean mFlag;
public Account(String accountNo, double balance) {
mAccountNo = accountNo;
mBalance = balance;
}
public double getBalance() {
return mBalance;
}
/**
* 取钱
*
* @param drawAmount
*/
public void draw(double drawAmount) {
mLock.lock();
try {
if (!mFlag) {
mCondition.wait();
} else {
System.out.println(Thread.currentThread().getName() + "取钱:" + drawAmount);
mBalance -= drawAmount;
System.out.println(mAccountNo + "账户余额为:" + mBalance);
mFlag = false;
mCondition.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
/**
* 存钱
*
* @param depositAmount
*/
public void deposit(double depositAmount) {
mLock.lock();
try {
if (mFlag) {
mCondition.wait();
} else {
System.out.println(Thread.currentThread().getName() + "存钱:" + depositAmount);
mBalance += depositAmount;
System.out.println(mAccountNo + "账户余额为:" + mBalance);
mFlag = true;
mCondition.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
}
ReentrantLock主要利用CAS+AQS队列来实现。
CAS
Compare and Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”该操作是一个原子操作,被广泛的应用在Java的底层实现中。Java并发包(java.util.concurrent)中大量使用了CAS操作。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。
AQS
AbstractQueuedSynchronizer,是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的大量细节问题。
AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。
转载自:https://juejin.cn/post/7030132841673392164