ReentrantLock锁总结

ReentrantLock锁(显式锁)总结

1 概念

​ 在Java5之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile(volatile可以说是java虚拟机提供的最轻量级的同步机制,后面会有单独介绍)。在Java5之后增加了一种新的机制:ReentrantLock。它并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。

2 Lock与ReentrantLock

2.1 Lock

​ Lock是一个接口,首先看一下Lock的源码。

public interface Lock {

    /**
     * 获取锁,如果该锁不可用,则处于线程调度目的,当前线程将被禁用,并处于休眠状态,知道获取该锁位置
     */
    void lock();

    /**
     * 除非当前线程中断,否则获取锁
     * 如果有锁获取并立刻返回
     * 如果该锁不可用,则出于线程调度目的,当前线程将被禁用,并在发生以下两种情况之一之前出于休眠状态:1. 该锁由当前线程获取。2. 还有其他一些线程,并且支持中断获取锁
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 仅在调用时释放锁,才获取锁
     */
    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 返回绑定到此实例的新实例
     */
    Condition newCondition();
}

2.2 重入

​ 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由自己持有的锁,那么这个请求就会成功。

​ 重入意味着获取锁的操作粒度是“线程”,而不是“调用”。重入一个一种实现方法是:为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM会记下锁的持有者,并且将获取计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减,当计数值为0时,这个锁将被释放。

​ 重入进一步提升了加锁行为的封装性,简化了面向对象并发代码的开发。比如,在如下代码中:子类改写了父类的dosomething方法,然后调用父类的方法,此时如果没有可重入锁,这段代码将产生死锁。因为**这两个方法都带了synchronized,因此每个dosomething方法在执行前都会获得Widget上的锁。**然而,如果内置锁是不可重入的,那么在调用super.doSomething时将无法获得Widget上的锁,因为这个锁已经被持有了,从而线程会永远的停顿下去,等待一个永远也无法获得的锁。重入则避免了这种死锁情况的发生。

public class Widget {
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling dosomething");
        super.doSomething();
    }
}

比如我们new一个对象:Widget widget = new LoggingWidget();由于dosomething是synchronized方法,因此widget对象是锁住的,因此它再访问super的dosomething方法,如果这个锁不可重复,那么将会一直等待。。。。。

2.3 ReentrantLock

​ ReentrantLock实现类Lock接口,并提供了与synchronized相同的互斥性和内存可见性。在获取ReentrantLock时,有着与进入同步代码块相同的内存语义,在释放ReentrantLock时,同样有着与退出同步代码块相同的内存语义。此外与synchronized一样,ReentrantLock还提供了可重入的加锁语义。ReentrantLock支持在Lock接口中定义的所有获取锁模式,并且与synchronized相比,它还为处理锁的不可用性问题提供了更高的灵活性。

​ 看到这里,很多同学会有一个疑问:为什么要创建一个与内置锁如此相似的新加锁机制?在大多数情况下,内置锁都能很好的工作,但在功能上存在一些局限性,例如:**无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限的等待下去。**内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,但却无法实现非阻塞结构的加锁规则。这些都是使用synchronized的原因,但在某些情况下,一种更灵活的加锁机制通常能提供更好的活跃性或性能。

​ ReentrantLock在加锁和内存上提供的语义和内置锁相同,此外它还提供了一些其他功能:包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁。ReentrantLock加锁和解锁的过程需要手动进行,不易操作,但非常灵活。似乎ReentrantLock的性能优于synchronized,并且确实Java5远远胜出,但是java6已经差不多了,虽说作者建议用ReentrantLock来替换synchronized,但是由于ReentrantLock的危险性比同步机制要高,如果忘记在finally块中调用unlock,就已经埋下了一颗定时炸弹。

2.4 如何在内置锁和显式锁之间选择

​ 在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括,可定时的、可轮询的与可中断的锁获取操作,公平队列以及非块结构的锁。否则,还是应该优先使用synchronized。

2.5 使用方式

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
  while(条件判断表达式) {
      condition.wait();
  }
 // 处理逻辑
} finally {
    // 需要手动显式的释放锁
    lock.unlock();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值