- 扯皮:
上篇的CountDownLatch可能太过简单的理论了,几乎0代码了。不太适合0开始学习的。这篇尽量详细一些,如果太详细全是代码的文章也很难受!所以写文章还真是个技术过。
我只知道synschonized锁为什么还要知道ReentantLock呢?,就拿现实中的真锁来说,锁大门的 用大锁,锁小门的用小锁,锁文具盒的用迷你锁,没有真正的好,只有真正的适合!也就是没有一种锁适用于所有场景! - Reentantlock
- Reentant:百度翻译 意思是:【再进去的】
- 所以这个类起个名就是:可重入锁。但是synschonized也是具有可重入性,他叫啥名,他就叫synschonized。或者更准确些就是Reentantlock就叫Reentantlock,但是具有可重入性。
- 分类(公平、不公平)
不公平锁:public static ReentrantLock lock = new ReentrantLock();
公平锁: public static ReentrantLock lock = new ReentrantLock(true);
为啥呢?直接看构造方法,不解释。在此不讨论公平不公平的事!后文继续研究!public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = (fair)? new FairSync() : new NonfairSync(); }
- 用法:
用法很简单:加锁,解锁即可!
想用公平锁或非公平锁都行,根据业务场景由你定!
然后就保证了这段业务逻辑代码的多线程环境下的安全问题。
lock.lock();
业务逻辑代码区域…
finally {
lock.unlock();
}
- 分析背后的源码,不公平锁为例
lock后,直接进入到这里。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
1000个线程同时执行了lock.lock(); 然后进入到这个 方法里。
compareAndSetState(0, 1),AQS实现的CAS操作,最快的那个线程执行了这句话,把state的值设置为1了。然后返回true。setExclusiveOwnerThread(Thread.currentThread());把一个AQS的变量exclusiveOwnerThread设置为当前线程。然后这个线程就去执行业务逻辑代码了。
接下来剩余的999个线程执行 compareAndSetState(0, 1),执行失败,因为第一个线程把state设置为1了,希望他的值是0,无法执行成功,返回false。
剩下的999个线程执行acquire(1);
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}`
tryAcquire(arg):执行以下方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//假设现在第一个线程没有完成 当前c==1 所以无法进入
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//刚刚线程1赋值的变量是线程1 假设我是剩下的999中的某个线程 所以在此无法进入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
假设线程1执行特别耗时的业务逻辑,一直没完成呢。所以这999个线程在上面的方法中只能返回false了。就到了上面的上面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
这两段代码总结一句话:这999个线程都得先后入队列,且都变成阻塞的状态 。只能等待被唤醒。不想把进入队列的代码粘贴过来了。线程1终于完成了业务逻辑处理任务。
执行了lock.unlock(); 方法了。线程1依次执行以下两个方法。
public final boolean release(int arg) {
if (tryRelease(arg)) {//线程1执行到这返回true
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒操作,细节代码不粘了,就是要去唤醒那个队列 了,先唤醒第一个,队列第一个线程唤醒后,开始了类似于刚刚线程1的执行线路了
unparkSuccessor(h);
return true;
}
return false;
}
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);//把那个AQS的变量置空了
}
setState(c);//线程1终于把state的值设置为0了。
return free;
}
- 还有一个问题
唤醒第一个线程后,它接下来往哪执行?再看下整体的代码。
所有的线程都是执行.lock()后进入阻塞队列的。被唤醒后肯定是接着往下执行业务逻辑代码了。白话:你当时执行到哪了,在哪进的队列(lock.lock()这个方法进去的),醒了后接着往下执行就是了。
lock.lock();
业务逻辑代码区域…
finally {
lock.unlock();
}
- 独占功能是如何体现的
在Node管理中:
在唤醒队列中的线程时,唤醒头结点后,好的,头结点醒了,继续执行业务任务去,等头结点完事后再回来唤醒现在的头结点。而不同于共享式的CountDownLatch。唤醒时一连串的全部唤醒。
结束
好多细节代码依旧还是没有,只是一个整体的思路。有了整体的认识,具体的细节就比较好看了 。