# 第三节 可重入锁

# 1、可重入

可重入锁又名递归锁,是指在同一个线程在外层方法拿到的钥匙,在调用内层方法时同样有效。Java 中 ReentrantLock 和 synchronized 都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

例如下列伪代码:

class A{
	public synchronized void aa{
		......
        bb();
        ......
	}
	public synchronized void bb{
		......
	}
}
A a = new A();
a.aa();

a 对象调用 aa() 方法时,持有钥匙:this。在 aa() 方法中再进一步调用 bb() 方法时仍然需要钥匙:this。此时如果不支持『可重入』,this在调用 aa() 方法时被持有,不能用于 bb() 方法开锁,必须等 aa() 方法执行完成,释放 this,才能用于调用 bb() 方法;但是不调用 bb() 方法 aa() 就没法执行完成,于是陷入死锁状态。

# 2、改进卖票案例

# ①改用可重入锁

public class SaleTicketDemo03ReentrantLock {

    public static void main(String[] args) {

        // 1.创建资源对象:TicketSynch(被各个线程锁争抢的资源对象)
        TicketReentrantLock ticket = new TicketReentrantLock();

        // 2.创建5个线程
        for (int i = 0; i < 5; i++) {

            new Thread(() -> {

                // 3.在线程内部循环卖票
                for (int j = 0; j < 22; j++) {

                    // 4.调用卖票方法
                    ticket.saleTicket();
                }

            }, "thread" + i).start();

        }

    }

}

class TicketReentrantLock {

    // 车票的库存数量
    private int tickectStock = 100;

    // Lock对象声明为成员变量,就可以被多个线程所共享。
    // 只要TicketReentrantLock类的对象是单例的,Lock对象就是单例的。
    // 各个线程必须拿到唯一的Lock对象作为钥匙才能开锁
    private Lock lock = new ReentrantLock();

    // 声明一个卖票的方法
    public void saleTicket() {

        // lock对象不能以局部变量形式来声明,因为这样会导致每个线程进入方法后都拿到一个开锁的钥匙
        // Lock lock = new ReentrantLock();

        // 调用Lock对象的lock()方法加锁
        lock.lock();

        if (tickectStock > 0) {

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + " 现在正在出售" + tickectStock + "号的车票,还剩" + (--tickectStock) + "张");
        } else {
            System.out.println(Thread.currentThread().getName() + " 卖完啦!!!");
        }

        // 调用lock对象的unlock()方法来解锁
        lock.unlock();

    }

}

# ②测试可重入性

为了测试可重入性,在案例中增加一个检查余票的方法。只要代码能够正常执行,没有陷入死锁,就说明可重入性生效。

class TicketReentrantLock {

    // 车票的库存数量
    private int tickectStock = 100;

    // Lock对象声明为成员变量,就可以被多个线程所共享。
    // 只要TicketReentrantLock类的对象是单例的,Lock对象就是单例的。
    // 各个线程必须拿到唯一的Lock对象作为钥匙才能开锁
    private Lock lock = new ReentrantLock();

    // 声明一个卖票的方法
    public void saleTicket() {

        // lock对象不能以局部变量形式来声明,因为这样会导致每个线程进入方法后都拿到一个开锁的钥匙
        // Lock lock = new ReentrantLock();

        // 调用Lock对象的lock()方法加锁
        lock.lock();

        // 为了测试可重入性,在当前同步方法中再调用另一个同步方法
        check();

        if (tickectStock > 0) {

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + " 现在正在出售" + tickectStock + "号的车票,还剩" + (--tickectStock) + "张");
        } else {
            System.out.println(Thread.currentThread().getName() + " 卖完啦!!!");
        }

        // 调用lock对象的unlock()方法来解锁
        lock.unlock();

    }

    public void check() {

        // 为了测试可重入性在当前方法再次锁定
        lock.lock();

        // 打印
        System.out.println("检查余票:\t" + ticketStock);

        // 解锁:如果这里忘记解锁,就会导致其他进程陷入无限等待
        lock.unlock();
    }

}

WARNING

注意:lock.lock()和lock.unlock()必须成对出现,否则将导致需要锁的线程无法执行。

上一节 回目录 下一节