详解JDK锁01:结合源码一文弄懂Lock接口!

详解JDK锁01:Lock接口

1. Lock简介

先引用Lock接口源码中作者贴的一段话

Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.

其实这段话就简单概括了Lock的三大优点:

  1. 灵活的结构:可以显式地获取与释放锁

  2. 多种不同的属性与方法

  3. 引入了 Condition 对象

接下来的部分将着重介绍这几点

2. Lock锁的灵活性

2.1 Lock接口方法

在 JDK5.0 之前,Java是借助于 Synchronized 关键字实现加锁功能,而这个功能是通过JVM实现的。而在 JDK5.0 之后,JUC包中新增了Lock接口实现锁功能。

虽然该Lock接口不具备 Synchronized 关键字隐式获取锁的便捷性,但是其提供了一系列手动操作锁的方法:

  1. 阻塞式地获取锁

该方法有一定的缺陷:如果当前锁被占用,那么当前线程将被禁用,进入阻塞状态,直到获取到锁为止。

void lock();
  1. 阻塞式地可打断地获取锁
void lockInterruptibly() throws InterruptedException;

虽然是阻塞式地获取锁,但是如果该线程被中断后,会抛出异常,停止继续阻塞。

  1. 非阻塞式地获取锁

尝试非阻塞地获取锁,调用该方法后立即返回

  • 若能够获取到锁,则返回 true

  • 若锁已被占用,则返回 false

boolean tryLock();

该方法的典型使用场景为:

// 伪代码
Lock lock = new ...;
if (lock.tryLock() {
    try {
        // 操作共享资源
    } finally {
        // 释放锁
        lock.unlock();
    }
} else {
    // 未获取到锁,执行其余操作
}
  1. 带有超时时间地获取锁

尝试在指定的一段时间内获取锁

  • 若在指定时间 time 内能够获取到锁,且未被中断,则返回 true

  • 若指定时间 time 结束后仍未获取到锁,则返回 false

  • 若在指定时间 time 内被打断,则抛出 InterruptedException

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

其中 time 代表指定的超时时间,unit 代表时间单位

  1. 释放锁
void unlock();

2.2 灵活性体现

使用 Synchronized 关键字进行获取锁与释放锁操作时:

当嵌套式地获取锁之后,其释放锁的顺序必须与获取锁的顺序相反

如下获取锁顺序为:lock1 -> lock2 -> lock3

释放锁顺序为:lock3 -> lock2 -> lock1

从外到内获取锁,从内到外释放锁

Object lock1 = new Object();
Object lock2 = new Object();
Object lock3 = new Object();
synchronized (lock1) {
    System.out.println("获取到lock1锁");
    synchronized (lock2) {
        System.out.println("获取到lock2锁");
        synchronized (lock3) {
            System.out.println("获取到lock3锁");
        }
    }
}

但是我们假设存在这一业务需求:

先获取锁A,再获取锁B,再释放锁A,再获取锁C,再释放锁B,再获取锁D。

这种获取锁的顺序与释放锁的顺序是不固定的,此时无法用 Synchronized 解决。

而采用Lock接口实现锁则可以完美解决这一问题,因为它提供了手动的加锁解锁方法!

3. Lock锁的多种功能

Lock接口中虽然只提供了简单的获取锁与释放锁的基本方法,但是其实现类ReentrantLock中实现了多种方法,提供了不同的功能。

这一篇文章只对Lock接口进行详细介绍,所以以下只做简单的文字介绍。

后续文章会通过源码解读 ReentrantLock

  1. 实现公平锁与非公平锁。实例化ReentrantLock时,其有参构造方法中传入的值为boolean类型。若传入值为true,为公平锁;否则为非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
  1. 判断锁是否已经被持有
public boolean isLocked() {
    return sync.isLocked();
}
  1. 判断锁是否为公平锁
public final boolean isFair() {  
    return sync instanceof FairSync;  
}

相较于Lock接口,Synchronized 只实现了非公平锁。

4. Condition基本使用

回顾 Synchronized 关键字,其实现 等待/通知 的模式是通过 Object 类内部的 waitnotify 以及 notifyAll 实现的。

Object lock = new Object();  
Thread thread1 = new Thread(() -> {  
    synchronized (lock) {  
        try {  
            lock.wait();  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("Thread1已被唤醒");  
    }  
});  
thread1.start();  
Thread.sleep(2000);  
synchronized (lock) {  
    System.out.println("唤醒Thread1");  
    lock.notify();  
    // lock.notifyAll();  
}

其中,notify 方法是唤醒 lock 锁上的其中一个线程,notifyAll 方法是唤醒 lock 锁上的全部线程。

然而,这两种方法均不能指定想要唤醒的线程。

Condition的出现很好地解决了这一问题,可以分组唤醒想要唤醒的线程。

如下为Condition的基本实现方式:需要使用 ReentrantLock 实现 Lock 接口

后续文章会详细解读 Condition

Lock lock = new ReentrantLock();  
Condition condition = lock.newCondition();  

new Thread(() -> {  
    lock.lock();  
    try {  
        try {  
            System.out.println("Thread1进入等待");  
            condition.await();  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("Thread1已被唤醒");  
    } finally {  
        lock.unlock();  
    }  
}).start();  

Thread.sleep(3000);  
new Thread(() -> {  
    lock.lock();  
    try {  
        System.out.println("唤醒Thread1");  
        condition.signal();  
    } finally {  
        lock.unlock();  
    }  
}).start();

5. Lock与Synchronized 对比

  1. Lock 所处的层面是JDK,是人为通过Java代码而实现的;而 Synchronized 是Java的关键字,是底层C++语言实现,处于JVM层面。

  2. Lock 获取和释放锁的顺序不固定,因为其内置了手动操作锁的方法;而 Synchronized 必须按照获取锁的相反顺序去释放锁。

  3. Lock 可以非阻塞式地获取锁( tryLock 方法);而 Synchronized 只能通过阻塞式地获取锁,若当前锁已被其他线程获取,那么该线程只能阻塞等待。

  4. Lock 既可实现公平锁,也可实现非公平锁;而 Synchronized只能实现非公平锁。

  5. lock 等待锁过程中可以用 lockInterruptibly 来中断等待,而synchronized只能等待锁的释放,不能响应中断;

6. 写在后面

参考文献:

  1. JDK5.0源码

  2. 《Java并发编程的艺术》

  3. 黑马Java并发编程教程

这个系列大概会有5篇左右的样子,我尽可能把自己对于JUC的理解通俗易懂地写出来

但如果有错误的地方,请大家指出来,我会及时去学习与改进~

如果大家觉得我的内容写的还不错,可以在评论区留言支持一下呀~

欢迎大家来逛一逛我的个人博客~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周三不Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值