广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
可重入锁:
ReentrantLock和synchronized都是可重入锁,下面是一个用synchronized实现的例子:
public class ReentrantTest implements Runnable {
public synchronized void get() {
System.out.println(Thread.currentThread().getName()); set(); }public synchronized void set() {
System.out.println(Thread.currentThread().getName()); }public void run() {
get(); }public static void main(String[] args) {
ReentrantTest rt = new ReentrantTest(); for(;;){ new Thread(rt).start(); } } }
整个过程没有发生死锁的情况,截取一部分输出结果如下:
Thread-8492
Thread-8492 Thread-8494 Thread-8494 Thread-8495 Thread-8495 Thread-8493 Thread-8493
set()和get()同时输出了线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。
不可重入锁
不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。
自定义模拟不可重入锁:
一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下:
package com.thread;import java.util.concurrent.atomic.AtomicReference;public class UnreentrantLock { private AtomicReferenceowner = new AtomicReference ();//记录当前锁的持有线程对象 public void lock() {//加锁 Thread current = Thread.currentThread();//获取当前线程对象 for (; ; ) {//自旋(被当前线程或其他线程持有锁,就会循环) if (owner.compareAndSet(null, current)) {//只有锁可用即为null,才能设置当前线程为锁持有对象,并返回true return; } } } public void unlock() {//解锁 Thread current = Thread.currentThread();//获取当前线程对象 owner.compareAndSet(current, null);//设置锁的持有对象为null }}
使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的。
自定义模拟可重入锁:
实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。稍微改一下,把它变成一个可重入锁:
package com.thread;import java.util.concurrent.atomic.AtomicReference;public class UnreentrantLock { private AtomicReferenceowner = new AtomicReference ();//记录当前锁的持有线程对象 private int state = 0;//记录重入次数 public void lock() {//加锁 Thread current = Thread.currentThread();//获取当前线程对象 if (owner.compareAndSet(null, current)) {//当前锁可用 state = 1;//状态置为1 return; } else { if (current == owner.get()) {//如果当前线程持有锁 state++;//重入次数加1 return; } for (; ; ) {//被其他线程持有就会继续循环 if (owner.compareAndSet(null, current)) {//只有锁可用即为null,才能设置当前线程为锁持有对象,并返回true return; } } } } public void unlock() {//解锁 Thread current = Thread.currentThread();//获取当前线程对象 if (current == owner.get()) {//如果当前线程持有锁 if (state > 0) {//重入次数大于0 state--;//重入次数减1 } else { owner.compareAndSet(current, null);//设置锁的持有对象为null } } }}
在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。
ReentrantLock中非公平的可重入锁实现:
// 非公平方式获取锁,用于tryLock()方法final boolean nonfairTryAcquire(int acquires) { //当前线程 final Thread current = Thread.currentThread(); // 继承至AbstractQueuedSynchronizer的方法 int c = getState();//获取锁状态值 //没有线程正在竞争该锁 if (c == 0) { // 继承至AbstractQueuedSynchronizer的方法 if (compareAndSetState(0, acquires)) {//若state为0则将state修改为acquires的值,状态0表示锁没有被占用 setExclusiveOwnerThread(current);// 设置当前线程独占 return true;// 成功 } } else if (current == getExclusiveOwnerThread()) {// 当前线程拥有该锁 int nextc = c + acquires;// 增加重入次数 if (nextc < 0) // overflow(计数值小于0,则抛出异常) throw new Error("Maximum lock count exceeded"); // 继承至AbstractQueuedSynchronizer的方法 setState(nextc);//设置锁状态值 return true;// 成功 } return false;// 失败}