并发之可重入锁与不可重入锁
锁:把需要的代码块,资源或数据锁上,只允许一个线程去操作,保证了并发时共享数据的一致性。锁有两种类型:可重入锁和不可重入锁。
不可重入锁
若当前线程执行中已经获取了锁,如果再次获取该锁时,就会获取不到被阻塞。下面我们以自旋锁设计一个不可重入锁
/* 不可重入锁 */public class NRSpinLock implements Ilock{private AtomicReference<Thread> currLock =new AtomicReference<>();@Overridepublic void lock(){Thread current = Thread.currentThread();while(!currLock.compareAndSet(null, current)){}}@Overridepublic void unlock (){Thread current = Thread.currentThread();currLock.compareAndSet(current, null);}}
我们用测试例子对使用不可重入锁类的情况做下分析
Ilock lock = new NRSpinLock();public void methodA(){lock.lock();methodB();lock.unlock();}public void methodB(){lock.lock();//do someThinglock.unlock();}
当线程执行methodA()方法首先获取lock,接下来执行methodB()方法,在methodB方法中,也尝试获取lock。当前线程的锁已经被methodA获取,由lock()代码可知,methodB无法获取到锁,并且自旋,产生了死锁。这种情况叫做不可重入锁。
可是我们平时又有需要重入一把锁的需求,怎么办呢?接下来我们对不可重入锁类进行改造。
可重入锁
/* 可重入锁 */public class RSpinLock implements Ilock {private AtomicReference<Thread> currLock =new AtomicReference<>();private int count =0;@Overridepublic void lock(){Thread current = Thread.currentThread();if(current==currLock.get()) {count++;return ;}while(!currLock.compareAndSet(null, current)){}}@Overridepublic void unlock (){Thread current = Thread.currentThread();if(current==currLock.get()){if(count != 0){count--;}else{currLock.compareAndSet(current, null);}}}}
由上段代码可知,当前线程尝试获取lock时候,首先判断当前线程是否已经获取了锁。如果获取则上锁次数count加1,然后返回继续执行其他代码。再来看看释放unlock,如果要求解锁的就是加锁的线程,那么上锁次数count减1;如果count等于0。就把这个锁完全解锁。
同样使用上述的例子对使用可重入锁类的情况做下分析
Ilock lock = new RSpinLock();public void methodA(){lock.lock();methodB();lock.unlock();}public void methodB(){lock.lock();//do someThinglock.unlock();}
当线程执行methodA()方法首先获取lock,接下来执行methodB()方法,在methodB方法中,也尝试获取lock。当前线程的锁已经被methodA获取,由lock()代码可知,count加1,并且返回,继续执行methodB代码块“do someThing”代码,最后释放锁unlock,count减1。跳出methodB后再次执行unlock方法,这个时候count等于0,所以currLock完全释放。
这样设计后拿到锁的代码能多次以不同的方式访问临界资源并把加锁次数count加1;在解锁的时候通过count,可以确保所有加锁的过程都解锁了。这就是可重入的能力。
可重入锁也叫递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
在java环境下,ReentrantLock和synchronized都是可重入锁。
