首先引入概念:
可重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁,
java里面最常见的锁,ReentrantLock和synchronized都是可重入锁
不可重入锁:不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。
如下图设计一个不可重入锁。
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 print(){ lock.lock(); doAdd(); lock.unlock(); } public void doAdd(){ lock.lock(); //sout("执行业务代码") lock.unlock(); } }
场景说明:假设某业务下需要调用Test类里面的print()方法,假设他的线程命名为T0,这时T0会执行lock.lock()方法,首先对于这个对象来说,isLocked属性的初始值时false,因此它进入while循环的时候
判断为false,直接跳出当前循环,把对象的isLocked属性变为true,相当于拿到了锁,这时T0再去执行doAdd(),由于要保证原子性,因此在doAdd方法里面也加入了lock锁,这时,线程还是T0线程,但
由于isLocked属性由于第一次加锁已经变成true,因此,T0线程执行到了wait()方法就处于等待,导致doAdd里面的业务代码无法执行,导致线程阻塞。
下面我们来创建一个可重入锁
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(); } } } }
public class Test{ Lock lock = new Lock(); /** 调用打印的方法 */ public void print(){ lock.lock(); doAdd(); lock.unlock(); } public void doAdd(){ lock.lock(); //sout("执行业务代码") lock.unlock(); } }
场景如上描述,假设线程T0进来了,调用print方法,lock.lock(),第一步首先拿到当前线程,由于初始的islocked为false,同时lockedby为null 和当前线程T0不相等,false &&true 得到还是false ,因此直接跳出while循环,线程不等待,将isLocked设置为true,同时设置当前锁的数量从0加上1变成1,并且设置lockby为当前线程T0,此时T0继续执行doAdd方法,当执行doAdd()里面的lock.lock()时,同样还是线程T0,因此while循环的判断变成了true&& false,最终拿到的还是false,这时线程还是不等待,isLocked还是true,同时当前线程拥有的锁变成了2,lockedby还是T0,这时假设又有T1,T2线程进来,当他们执行print()方法,执行到了lock.lock(),首先拿到当前线程是T1,而lockedby是T0,while循环的条件判断是true&&true,则T1就处于了等待状态,只有当T0执行完doAll()的业务代码,并第一次释放锁,lock.unlock(),当前线程的计数器减去1,这时T0再去执行print方法里面的lock.unlock(),这时线程T0,计数器变量变成了0,同时设置isLocked为false,执行notify方法,唤醒其他的线程,后续线程抢夺资源拿到锁之后,即可实现同步安全的执行。
总结如下:
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
不可重入锁,也可以叫非递归锁,就是拿不到锁的情况会不停自旋循环检测来等待,不进入内核态沉睡,而是在用户态自旋尝试。
同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。