java 可重入锁、不可重入锁

 可重入锁 

广义上的可重入锁指的是可重复进入(多次进入)的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。(前提得是同一个对象或者Class)

我的理解就是,某个线程已经获得某个锁,可以无需等待而再次获取锁,过程中不会出现死锁(对于同一个线程而言)。

简单的说,就是同一个线程可无限次地进入同一把锁的不同代码。

常见的可重入锁

Synchronized 和 ReentrantLock 都是可重入锁。

可重入锁的释放

同一个线程获取同一个锁,状态值state会累加,假设state累加到了2,每释放一次锁会减1,只有当状态值state减到0了,其他线程才有机会获取锁。也就是说,state归零才是已释放锁的标致。

可重入锁示例

@SuppressWarnings("all")
public class ReentrantTest implements Runnable {

    @Override
    public void run() {
        get();
    }

    // 同一时刻,只有一个线程进入。
    public synchronized void get() {
        System.out.println("[get]:" + Thread.currentThread().getName());
        // 同一个线程执行的时候,set方法和get方法是同一把锁,可以再次获取锁而不需要等待。
        set();
    }

    // 同一时刻,只有一个线程进入。且同一个线程会先后执行get方法和set方法。
    // 注意:get方法和set方法是同一把锁,都是锁的当前对象,即调用对象。
    public synchronized void set() {
        System.out.println("[set]:" + Thread.currentThread().getName());
    }

    /**
     * 在get方法内执行set方法时,会再次请求同一把锁,因为synchronized是可重入锁,所以又可以得到该锁。循环这个过程。
     * 假设不是可重入锁的话,那么请求的过程中会出现阻塞,从而导致死锁。
     * 所以,从这里可以看到可重入锁的作用:避免死锁的发生。
     *
     * @param args
     */
    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        // for(;;) 模拟无限循环
        // 开启多个线程
        for (; ; ) {
            new Thread(rt).start();
        }
    }
}

分析:在get方法内执行set方法时再次请求同一把锁,因为synchronized是可重入锁,所以又可以得到该锁。循环这个过程。假设不是可重入锁的话,那么请求的过程中会出现阻塞,从而导致死锁。

死锁

多线程中,不同的线程都在等待其它线程释放锁,而其它线程由于一些原因迟迟没有释放锁。程序的运行处于阻塞状态,不能正常运行也不能正常终止。

运行结果

set() 和 get() 同时输出了相同的线程名称,也就是说某个线程执行的时候,不仅进入了set同步方法,还进入了get同步方法。使用 synchronized 关键字没有发生死锁,证明其是可重入的锁。

可重入锁的实现原理?

每一把锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增1;当线程退出同步代码块时,计数器会递减1,如果计数器到 0,则释放该锁。

再分析一下上面可重入锁的例子

同一个线程先后调用两个方法,调用set方法时,计数器会变为2,整个set方法调用执行完,先退出内层执行减1,再退出外层执行减1。计数器归零,然后释放锁。

不可重入锁

就是某个线程已经获得某个锁,之后不可以再次获取锁,会被阻塞。

设计一个不可重入锁

public class Lock {
    private boolean isLocked = false;
    /**
     * 加锁
     */
    public synchronized void lock() throws Exception{
        while(isLocked){
            //当前线程释放锁,让出CPU,进入等待状态,直到被唤醒,才继续执行15行
            wait();
            System.out.println("wait");
        }
        isLocked = true;
    }
    /**
     * 解锁
     */
    public synchronized void unlock(){
        isLocked = false;
        //唤醒一个等待的线程继续执行
        notify();
    }
}

 测试

public class Test {
    Lock lock = new Lock();
    public void print() throws Exception{
        //加锁 标记为true
        lock.lock();
        //释放锁->等待 阻塞在16行
        doAdd();
        lock.unlock();
    }
    public void doAdd() throws Exception{
        lock.lock();
        System.out.println("doAdd");
        lock.unlock();
    }

    public static void main(String[] args)throws Exception {
        Test test=new Test();
        test.print();
    }
}

结果:这里,虽然模拟的是不可重入锁,实际还是在单线程环境中的。当前线程执行print()方法首先加锁 标记为true,接下来释放锁->等待 阻塞在16行内部的14行。整个过程中,第一次进入lock同步方法,执行完毕,第二次进入lock同步方法,阻塞等待。这个例子很好的说明了不可重入锁。  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值