前言
一个月之前就备好了标题,然后就没有然后了,以至于zk的分布式锁都收尾了,这里还是单单的一只图片。都说自律的人最可怕,我这不自律的让自己都害怕,趁着周末,赶紧撸一发。
案例
在秒杀案例中,我们使用ReentrantLock来保证商品不会被超卖,以下是伪代码:
private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁
@Override
@Transactional
public Result startSeckilLock(long seckillId, long userId) {
try {
lock.lock();
//业务代码逻辑
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return Result.ok(SeckillStatEnum.SUCCESS);
}
不过由于经验不足,这个地方曾踩过一个大坑,详见:从构建分布式秒杀系统聊聊Lock锁使用中的坑。有兴趣的小伙伴可以了解一下,事务提交和锁释放的顺序导致超卖的问题。
介绍
参考JDK8官方文档给的解释,ReentrantLock具有和synchronized相同的基本行为和语义的可重入互斥,但具有扩展功能。
- 具有非阻塞锁特性,超时获取锁失败
- 提供公平锁、非公平锁参数
ReentrantLock的实现
创建锁:
/**
* 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) {
//默认为false非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
所谓公平锁,就是按照时间先后顺序,即先等待的线程先得到锁。而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。
公平锁:
/**
* 继承Sync,后面会提到
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
//调用AbstractQueuedSynchronizer的模板方法acquire
acquire会调用下面我们重写的这个tryAcquire
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取同步锁状态,volatile修饰保证其可见性
int c = getState();
//判断是否
if (c == 0) {
//检查在时间顺序上,是否有比自己早的线程,CAS设置state(基于unsafe底层)获取独占锁
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;
}
}
获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
自旋获取锁
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);
}
}
入队列addWaiter,新的节点从队尾插入,node的模式是Node.EXCLUSIVE,使用compareAndSetTail保证原子性,只有没获取到锁的线程才会入队列。
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;
}
非公平锁:
获取锁:
参考
https://blog.csdn.net/xxcupid/article/details/51891743
https://blog.csdn.net/ios99999/article/details/76977666
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html