首先我们这里提到的锁,是把所需要的代码块,资源,或数据锁上,在操作他们的时候只允许一个线程去做操作。最终结果是为了保证cpu计算结果的正确性。
对不可重入锁的理解:
代码语言:javascript复制public class Test{
Lock lock = new Lock();
public void methodA(){
lock.lock();
...........;
methodB();
...........;
lock.unlock();
}
public void methodB(){
lock.lock();
...........;
lock.unlock();
}
}
当A方法获取lock锁去锁住一段需要做原子性操作的B方法时,如果这段B方法又需要锁去做原子性操作,那么A方法就必定要与B方法出现死锁。这种会出现问题的重入一把锁的情况,叫不可重入锁。
A方法需要等B方法执行完才能解锁,但是B方法想执行完代码又必须要lock锁来加锁。A的锁未解锁前,其他代码块无法使用此锁来加锁。这是由这个不可重入锁决定的。
不可重入锁:
代码语言:javascript复制public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
那么平时我们又有需要重入一把锁的需求!!!!比如A方法是个原子性操作,但它有需要调用B方法的原子性操作,他们还争抢的是同一个临界资源,因此需要同一把锁来加锁(ps:争抢同一临界资源的实质就是对同一把锁的争抢),欢迎关注我们,公号IT码徒。
针对此情况,就有了可重入锁的概念:
可重入锁的实现:
代码语言:javascript复制public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread thread = Thread.currentThread();
while(isLocked && lockedBy != thread){
wait();
}
isLocked = true;
lockedCount ;
lockedBy = thread;
}
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
可以看见代码的核心概念是:
首先解释lockedBy:顾名思义,临界资源被哪个线程锁住了。
加锁时,先获取当前线程。(识别谁需要锁)
代码语言:javascript复制Thread thread = Thread.currentThread();
判断:当临界资源已被锁上,但当前请求锁的线程又不是之前锁上临界资源的线程。那么当前请求锁的线程需要等待。
代码语言:javascript复制while(isLocked && lockedBy != thread){
wait();
}
注意上面是个while,并且是个wait,因此当请求线程请求不到锁的时候,就wait了。
当时当while不满足有的3种情况:
A:当前锁没有线程使用.
B:当前锁有线程使用,当前请求锁的线程就是现在正在使用锁的线程。
C:当前锁没有线程使用,当前请求锁的线程就是现在正在使用锁的线程。(不可能出现,锁0没有被用,哪还有线程使用锁)
来看看
A:没有线程使用:
那么:
代码语言:javascript复制isLocked = true;
lockedCount ;
lockedBy = thread;
当前请求锁的线程先把锁加上,然后把上锁次数+1,然后把自己(本线程)赋值给lockedBy,以说明当前谁用了这把锁方便之后重入的时候做while判断。
再来看解锁:
代码语言:javascript复制public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
首先看看要求解锁的线程是不是当前用锁的线程。不是则什么也不做。(当然不能随意让其他的线程一执行unlock代码就能解锁使用啊。那这样相当于谁都有一把钥匙了,这里这个判断也就是说明解锁的必须是加锁的)
如果要求解锁的就是加锁的线程。
那么把加锁次数减一。
然后在判断加锁次数有没有变为0。
变为0说明,这个锁已经完全解锁了。锁上标识islocked可以复位了。
并且随机唤醒某个被wait()等待的线程 :notify()
这就是重入锁的设计。另外,关注Java技术精选公众号,回复“资料”,送你面试题宝典和大量视频资源下载!
它和不可重入锁的设计不同之处:
- 不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单
- 可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。
设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。
总结
这个重入的概念就是,拿到锁的代码能不能多次以不同的方式访问临界资源而不出现死锁等相关问题。经典之处在于判断了需要使用锁的线程是否为加锁的线程。如果是,则拥有重(chong)入的能力。