【并发编程】Lock 接口

锁是用来控制多个线程访问共享资源的方式,悲观锁能够防止多个线程同时访问共享资源,乐观锁可以允许多个线程并发的访问共享资源,但是只能有一个进行修改。

在 Lock 接口出现以前,Java 程序是靠 synchronized 关键字实现锁功能的,而 jdk 5之后,并发包中新增了 Lock 接口以及相关实现类来实现锁的功能,它提供了与 synchronized 关键字类似的同步功能。只是在使用的时候,需要显示地获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。

使用 synchronized 关键字将会隐式的获取锁,但是它将锁的获取和释放固化了,也即是先获取,再释放。当然这种方式简化了锁的管理,可是扩展性没有显示的获取锁和释放来的好。例如,针对一个场景,手把手进行锁的获取和释放,先获得锁A,然后再获取锁B,当锁B获得后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D,以此类推。这种场景下,synchronized关键字就不是那么容易实现了,而使用 Lock 确容易许多。

Lock 接口的特性

Lock 接口提供的 synchronized 关键字所不具备的主要特性

  1. 尝试非阻塞地获取锁

    尝试获取锁,获取成功返回 true,获取失败返回 false

  2. 能被中断的获取锁

    与 synchronized 不同,阻塞队列中的线程可被中断,中断异常将会被抛出

  3. 超时获取锁

    在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

Lock 接口的 API

Lock 是一个接口,它定义了锁获取和释放的基本操作,其 API 如下:

// 阻塞式获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回
void lock();

// 可中断的获取锁,和lock方法不同之处在于该方法会响应中断
void lockInterruptibly() throws InterruptedException;

// 尝试非阻塞获取锁,调用该方法后立即返回。如果能够获取到返回true,否则返回false
boolean tryLock();

// 超时获取锁,当前线程在以下三种情况下会被返回:
// 当前线程在超时时间内获取了锁
// 当前线程在超时时间内被中断
// 超时时间结束,返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

// 释放锁
void unlock();

// 获取等待通知组件,该组件和当前的锁绑定
// 当前线程只有获取了锁,才能调用该组件的await方法,而调用后,当前线程将释放锁
Condition newCondition();

Lock 接口的实现类

ReentrantLock:可重入锁

ReentrantReadWriteLock:读写锁

jdk1.7 ConcurrentHashMap 中的 Segment 继承了 ReentrantLock

Lock是 jdk 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。

使用方法:

多线程下访问(互斥)共享资源时, 访问前加锁,

访问结束以后解锁,解锁的操作推荐放入 finally 块中。

// 根据不同的实现 Lock 接口类的构造函数得到一个锁对象 
Lock lock = ...;
// 获取锁位于 try 块的外面 
lock.lock();
try { 
      // 访问共享资源
} finally { 
     lock.unlock(); 
}

注意:

加锁位于对资源访问的 try 块的外部,特别是使用 lockInterruptibly 方法加锁时就必须要这样做,这为了防止线程在获取锁时被中断,这时就不能释放锁。

实现 Lock 接口的基本思想

实现锁的功能,需要两个必备元素:

  • 一个是表示锁状态的变量(假设0表示没有线程获取锁,1表示已有线程占有锁),该变量必须声明为 volatile 类型;
  • 一个是队列,队列中的节点表示因未能获取锁而阻塞的线程

为了解决多核处理器下多线程缓存不一致的问题,表示状态的变量必须声明为voaltile类型,并且对表示状态的变量和队列的某些操作要保证原子性和可见性。原子性和可见性的操作主要通过 Atomic 包中的方法实现。

线程获取锁的大致过程,不考虑可重入、中断或超时

  1. 读取表示锁状态的变量

  2. 如果表示状态的变量的值为0,那么当前线程尝试将变量值设置为1,通过CAS操作完成

    • 若成功,表示获取了锁,如果该线程已位于在等待锁的队列中,则将其出列,并将下一个节点则变成了队列的头节点

    • 如果该线程未入列,则不用对队列进行维护

    • 然后当前线程从 lock 方法中返回,对共享资源进行访问

    • 若失败,则当前线程将自身放入等待锁的队列中并阻塞自身,此时线程一直被阻塞在 lock 方法中,没有从该方法中返回(被唤醒后仍然在 lock 方法中,并从下一条语句继续执行,这里又会回到第1步重新开始)

  3. 如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞(被唤醒后仍然在 lock 方法中,并从下一条语句继续执行,这里又会回到第1步重新开始)

注意:唤醒并不表示线程能立刻运行,而是表示线程处于就绪状态,仅仅是可以运行而已

线程释放锁的大致过程

  1. 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点,释放锁的线程从就从 unlock 方法中返回,继续执行线程后面的代码
  2. 被唤醒的线程(队列中的队首节点)和可能和未进入队列并且准备获取锁的线程竞争锁,重复获取锁的过程

注意:可能有多个线程同时竞争去获取锁,但是一次只能有一个线程去释放锁,队列中的节点都需要它的前一个节点将其唤醒,例如有队列 A、B、C ,即由A释放锁时唤醒B,B释放锁时唤醒C

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值