Lock锁之可重入锁

Lock 是一个接口,要求实现该接口的类可以做到同步的功能。

Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

下面是lock接口的常用api,下面主要介绍其中一个实现ReentrantLock(可重入锁)

public interface Lock {
    void lock();//获得锁
    //获取锁定,除非当前线程是interrupted
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();//只有在调用时才可以获取锁
    //如果在给定的时间内是空闲的,并且当前的线程尚未得到释放,即可获取锁
    boolean tryLock(long time, TimeUnit unit)throws InterruptedException;
    void unlock();//释放锁
    Condition newCondition();//返回一个新的Condition绑定到Lock实例
}

可重入锁

ReentrantLock类是一个实现可重入锁的类,“可重入锁” 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。

Lock方法

我们来看个简单的demo

public class Demo {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        lock.lock();
        new Thread(()->{
            lock.lock();
            System.out.println(1);
        }).start();
        System.out.println(2);
        //lock.unlock();
    }
}

当我们的线程调用lock方法后,我们可以认为该线程就持有该锁了,调用lock方法,锁对象会将标志位改为加锁状态(0变为1,通过CAS保证原子性,可以参考我上篇文章),同时记录持有锁的线程信息(后续用于重入)

在上面这段代码,1是无法被打印的,因为子线程会阻塞在lock.lock()这一步,由于主线程获取到了锁,子线程再去获取锁的时候,发现标志位已经是1(加锁状态),然后去判断当前持有锁的是不是该线程,如果不是,就会进入一个阻塞状态,等待lock锁被unlock出去,重新去抢夺

unLock方法

因为是可重入锁,所以执行一个lock就要执行一个unlock,lock锁内部会有个计数器,lock一次计数器+1,unlock一次-1,只有计数为0,才认为锁被释放,然后唤醒阻塞的线程去争夺锁。

这里涉及一个公平锁和非公平锁,ReentrantLock默认是非公平锁,非公平锁即不按照线程等待的时间来获取锁(即不采取先到先得的形式),非公平锁状态下,一个线程尝试获取锁,如果锁被占有,那么该线程会进入阻塞,并将其维护在阻塞队列的末尾,等锁被释放,从这个队列的头部开始唤醒线程来获取锁。

那为什么会不公平呢,唤醒线程的顺序是从头到尾,然后插入也是插入到后面,这样不是满足先到先得嘛?理论上是这样的,但是ReentrantLock的非公平锁在底层实现的时候,是不会去判断队列的情况的

比如,一个锁被释放,那么就会唤醒队列中的第一个线程(比如是线程A),结果这个时候,有个线程B来抢夺锁(因为先把标志位改为0,再去唤醒线程,所以是存在这种情况的),线程B上来直接判断锁是不是被释放了,如果释放了直接获取,然后线程A醒来后去获取发现锁又没了,然后又去队尾排队了,这样就显得非常不公平了

通过ReentrantLock的构造方法来指定fair参数为true。其获取锁和释放锁的代码其实跟非公平锁差不多,唯一一个细节就是,每个线程在获取锁的时候,会判断一下队列里是否存在等待中的线程。

可以看到判断标志位是0(无锁),还会去判断队列是否存在线程,如果存在,直接就返回false,然后退出到外层逻辑(这里不展示外层的逻辑,但是可以明确的是,如果返回时false,外层就会把这个线程加入等待队列),下面这段代码就可以体现公平锁和非公平锁的区别了

public class Demo {

    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        lock.lock();
        new Thread(()->{
            lock.lock();
            lock.unlock();
        }).start();
        Thread.sleep(1000);
        lock.unlock();
        lock.lock();
    }

}
公平锁/非公平锁

公平锁即先到先得,非公平锁即不遵守这个原则,即获取到锁的几率与你等待的时间无关,synchronized就是一个非公平锁,因为当锁变量释放,jvm是从阻塞队列随机挑选一个线程来获取锁的,所以很容易出现线程“饥饿”的情况,一般使用公平锁就使用Lock锁来实现

tryLock方法

tryLock方法会返回一个boolean值,如果获取成功则返回true,否则为false,tryLock如果获取到锁也是要将其释放的掉,相当于lock成功,但是如果获取失败,tryLock不会将线程阻塞(不会加入等待队列),所以这是一个利于我们自定义业务逻辑的方法。

同时tryLock可以传入一个时间,在该时间内,只要获取到锁就会返回true,如果时间到了还没获取到则返回false

Lock锁还有个类似等待唤醒的机制,通过condition接口类实现,这里不作赘述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值