原文地址
首先我们这里提到的锁,是把所需要的代码块,资源或数据锁上,在操作它们的时候只允许一个线程去操作。最终结果是为了保证cpu计算结果的正确性。
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();
}
}
不可重入锁的使用示例
public class Test{
Lock lock = new Lock();
public void methodA(){
lock.lock();
...........;
methodB();
...........;
lock.unlock();
}
public void methodB(){
lock.lock();
...........;
lock.unlock();
}
}
这里的锁是不可重入锁,执行A方法时,A方法获取lock锁之后,需要调用B方法,但是此时该对象的对象锁已经被方法A拿走了,B方法得不到需要的对象锁,只能等待A方法将对象锁释放,而A方法又必须调用完B方法才能释放对象锁,这样子就会出现死锁,这种很笨的锁就叫做不可重入锁,上述情况就是这种锁可能会出现缺陷。
接下来我们讲一讲可重入锁
可重入锁的实现:
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();
}
}
}
}
让我们来对可重入锁的代码实现做一下解析:
首先是lockBy:顾名思义,临界资源被哪个线程锁住了。
加锁时,先获取当前线程,(识别谁需要锁)
Thread thread = Thread.currentThread();
判断:当临界资源已被锁上,但当前请求锁的线程不是之前锁上临界资源的线程。那么当前请求锁的线程需要等待。
while(idLocked&&lockedBy!=thread){
wait();
}
注意上面是个while,并且是个wait,因此当请求线程请求不到锁时,就只能wait了。
while循环条件不满足有2种情况:
-
当前锁没有被其他线程使用
-
当前锁有线程使用,但是当前请求锁的线程就是现在正在使用锁的线程。
当while循环条件不被满足时,发生了什么呢?
isLocked = True;
lockedCount++;
lockBy=thread;
当前请求锁的线程先把锁锁上,然后把上锁次数 + 1(因此可重入锁需要自己释放锁),然后把自己(本线程)赋值给lockBy,用以说明当前谁用了这把锁,方便以后重入的时候做while判断。
再来看解锁:
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
首先看看要求解锁的是不是当前用锁的线程,不是则说明都不做。(当然不能随意让其他的线程解锁啊,那样子上锁还有什么意义,这里也说明解锁的线程必须是上锁的那个线程,解锁还需上锁人)
如果是上锁的线程要求解锁,那么把加锁次数减一,然后再判断加锁次数有没有变为0,变为0则说明这个锁已经完全解锁了,上锁标识isLocked可以复位了,并使用notify()随机唤醒某个被wait()等待的线程。
这就是可重入锁的实现思想和实现方法
它和不可重入锁的不同之处:
不可重入锁:只判断这个锁有没有被锁上,只要被锁上了,那么无论当前申请锁的是已获取当前锁的线程还是未获得当前锁的线程,统统必须等待,实现较为简单。
可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当上锁的就是当前请求锁的线程时,当前线程可以再次访问临界资源,并且把加锁次数加一。
设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。
总结:这个可重入的概念就是,拿到锁的线程可以多次以不用的方式再次访问临界资源而不出现死锁的情况。经典之处在于判断了当前申请锁的线程是否是加锁的线程。如果是,则拥有重(chong)入的能力