ReentrantLock
ReentrantLock 的基础使用
public static void main(String[] args) throws InterruptedException {
// 构造函数不传参数为非公平锁,传true为公平锁
ReentrantLock reentrantLock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
try {
System.out.println("t1 start");
// 正常加锁
reentrantLock.lock();
Thread.sleep(5000);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 正常解锁
reentrantLock.unlock();
}
});
Thread thread2 = new Thread(() -> {
boolean whether = false;
try {
System.out.println("t2 start");
// 尝试加锁 5S, true 获取到所,false 就是没获取到
whether = reentrantLock.tryLock(5, TimeUnit.SECONDS);
System.out.println("t2 end");
} catch (InterruptedException e) {
System.out.println("tryLock中断了");
} finally {
if (whether) {
reentrantLock.unlock();
}
}
});
thread1.start();
thread2.start();
Thread.sleep(100);
// 中断 thread2 会抛出 InterruptedException,但是就不会继续执行 t2 end
// 如果不中断五秒后会执行 t2 end
thread2.interrupt();
}
ReentrantLock 和 synchronized 的区别
- ReentrantLock 可以尝试获取锁,获取不到也可以继续往下执行
- ReentrantLock 可以为公平锁。
- 公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。保证老的线程排队使用锁,新线程仍然排队使用锁。
- 非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁。
- ReentrantLock 可中断,通过 tryLock 和 lockInterruptibly 方法加锁,可以被中断,中断之后抛出 InterruptedException,可以捕获 InterruptedException 进行对应逻辑处理
- ReentrantLock 是 api 层面解决同步问题,而 synchronized 是关键字,在 JVM 层面实现的加解锁
ReentrantLock 结构
源码解析
- 在 new ReentrantLock 的时候,会判断创建公平锁还是非公平锁
Sync 继承了 AbstractQueuedSynchronizer(AQS):CAS + volatile + LockSuport。
public ReentrantLock(boolean fair) {
// fair 为 true 就是公平锁,否则就是非公平锁
// 不同的锁加锁方式不一样
sync = fair ? new FairSync() : new NonfairSync();
}
- lock 方法就是执行 sync.lock(),而 sync 就是刚开始创建的非公平锁或者公平锁
- 加锁解锁其实就是对 state 属性的操作,state = 0 说明没锁,state > 0 就说明被线程抢到锁了。 state 是 AQS 十分重要的属性。
lock 方法
这里主要写 公平锁 FairSync 的 lock 方法。
非公平锁(NonfairSync)的 lock 方法大部分方法与 公平锁的 lock 方法一样
最大的区别就是:尝试获取锁(tryAcquire)的时候,如果这时候刚好锁被释放,那么不管队列里有没有线程,直接就获取到锁,如果也获取不到,才会放到队列里去。主要就是两个方法:tryAcquire() 和 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- tryAcquire():尝试获取锁,修改 state 的值,大于 0 为已加锁
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):让该线程入队,进入 park 状态,等待 unpark
final void lock() {
// 只执行 acquire 方法
acquire(1);
}
public final void acquire(int arg) {
//1、尝试获取锁
//2、入队
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 如果线程被中断或别的情况,才会执行这个方法,调用线程的 interrupt 方法
selfInterrupt();
}
首先看 tryAcquire 方法
/**
* 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();
int c = getState();
if (c == 0) {
// 1.hasQueuedPredecessors() 就是判断当前线程是否需要排队,如果不需要,就继续往下执行
// 2.compareAndSetState 是用 CAS 的方式设置 state(锁的同步状态),防止其他线程已经更改 // 过这个状态
// 3. setExclusiveOwnerThread 就是设置当前占用锁的线程是当前线程,到这就加锁成功
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前拿到锁的线程是这个线程,重新设置state 的值,一般是 state + 1,表示重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果拿到锁就返回了,如果拿不到执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 创建 Node
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;
}
}
// AQS 里的 tail 和 head 本来都是空的,这个是创建 head 和tail,如果被别的线程创建了 head
// 就会执行上面 if (pred != null) 里同样的逻辑
enq(node);
return node;
}
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;
}
// 上面那一步没有获取到锁,就尝试 park 线程,会执行这个方法一次,再重新执行 for // 循环
// 第一次:告诉前置节点释放锁的时候通知一下自己
// 即 CAS 方式设置前置节点的 waitStatus = -1,也许前置节点刚释放锁
// 当然把 waitStatus 设置成功之后,可能前置节点变为 head 节点了,所以接下来还会进 // 行第二次操作,那样就不会在执行第二次这个方法了
// 第二次:已经告诉前置节点释放锁的时候通知自己,那么可以安心 park,也不需要担心执 // 行到这一步的时候,前置节点不仅变成了 head 而且执行完并且已经释放锁了
// 因为 LockSuport.unpark执行后,这个线程处于 start 后的状态,就不会再 park 了
// 具体见 unlock 方法,waitStatus 不为 0 会唤醒后一个节点
if (shouldParkAfterFailedAcquire(p, node) &&
// 调用 LockSuport.park 阻塞线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 线程被中断等情况、而且没获取到锁退出死循环执行的方法
if (failed)
cancelAcquire(node);
}
}
tryLock 方法
- tryLock() 是非公平锁的 tryAcquire() 实现
- tryLock(long timeout, TimeUnit unit) 非公平锁和公平锁是不同的,只是阻塞的时候调用的是 LockSuport.parkNanos 方法,到指定时间获取不到就返回了
unlock 方法
public void unlock() {
sync.release(1);
}
// release 方法
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 如果当前节点的 waitStatus != 0,唤醒后置节点
// 如果没有后置节点入队列,那么waitStatus 就是0 这里只唤醒后置节点
// 加锁的时候把 waitStatus 设置为 -1 ,在这里用上了
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease 尝试释放锁,就是 state - 1,所以如果连续两次 lock,那么释放的时候就得调用两次 unlock 不然释放失败,别的线程再次尝试加锁还是会被阻塞。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
Condition
使用await需要先获取到了锁才可使用。
Condition自己也维护了一个链表,如果await,那么当前线程释放锁,放入自己的链表中,等到singal之后,会释放,被唤醒,继续执行下面的代码。
Condition 使用
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
try {
System.out.println("我进入了T1!");
// 阻塞
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("释放T1锁!");
}
});
Thread t3 = new Thread(() -> {
lock.lock();
try {
System.out.println("我进入了T3!");
// 阻塞
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("释放T3锁!");
}
});
t3.start();
t1.start();
Thread.sleep(2000);
Thread t2 = new Thread(() -> {
lock.lock();
try {
System.out.println("我进入了T2!");
// 唤醒调用过 condition.await 方法的一个线程
condition.signal();
System.out.println("我是T2,我唤醒了T1或T3,T1或T3进入等待状态!");
// 唤醒全部线程
//condition.signalAll();
} finally {
lock.unlock();
System.out.println("释放T2锁!");
}
});
t2.start();
}
和 notify 的区别
notify 唤醒的线程是随机的一个线程,如果有消费者和生产者,Condition 可以根据不同的 Condition 只唤醒消费者或者生产者。