线程间的同步与通讯(4)——Lock 和 Condtion

前言

系列文章目录 java

前面几篇咱们学习了synchronized同步代码块,了解了java的内置锁,并学习了监视器锁的wait/notify机制。在大多数状况下,内置锁都能很好的工做,但它在功能上存在一些局限性,例如没法实现非阻塞结构的加锁规则等。为了拓展同步代码块中的监视器锁,java 1.5 开始,出现了lock接口,它实现了可定时、可轮询与可中断的锁获取操做,公平队列,以及非块结构的锁。segmentfault

与内置锁不一样,Lock是一种显式锁,它更加“危险”,由于在程序离开被锁保护的代码块时,不会像监视器锁那样自动释放,须要咱们手动释放锁。因此,在咱们使用lock锁时,必定要记得:
在finally块中调用lock.unlock()手动释放锁!!!
在finally块中调用lock.unlock()手动释放锁!!!
在finally块中调用lock.unlock()手动释放锁!!!工具

Lock接口

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();
    
    Condition newCondition();
}

典型的使用方式:学习

Lock l = ...;
l.lock();
try {
    // access the resource protected by this lock
} finally {
    l.unlock();
}

锁的获取

Lock接口定义了四种获取锁的方式,下面咱们一个个来看this

  • lock()spa

    • 阻塞式获取,在没有获取到锁时,当前线程将会休眠,不会参与线程调度,直到获取到锁为止,获取锁的过程当中不响应中断
  • lockInterruptibly()线程

    • 阻塞式获取,而且可中断,该方法将在如下两种状况之一发生的状况下抛出InterruptedExceptioncode

      • 在调用该方法时,线程的中断标志位已经被设为true了
      • 在获取锁的过程当中,线程被中断了,而且锁的获取实现会响应这个中断
    • 在InterruptedException抛出后,当前线程的中断标志位将会被清除
  • tryLock()对象

    • 非阻塞式获取,从名字中也能够看出,try就是试一试的意思,不管成功与否,该方法都是当即返回的
    • 相比前面两种阻塞式获取的方式,该方法是有返回值的,获取锁成功了则返回true,获取锁失败了则返回false
  • tryLock(long time, TimeUnit unit)blog

    • 带超时机制,而且可中断
    • 若是能够获取带锁,则当即返回true
    • 若是获取不到锁,则当前线程将会休眠,不会参与线程调度,直到如下三个条件之一被知足:

      • 当前线程获取到了锁
      • 其它线程中断了当前线程
      • 设定的超时时间到了
    • 该方法将在如下两种状况之一发生的状况下抛出InterruptedException

      • 在调用该方法时,线程的中断标志位已经被设为true了
      • 在获取锁的过程当中,线程被中断了,而且锁的获取实现会响应这个中断
    • 在InterruptedException抛出后,当前线程的中断标志位将会被清除
    • 若是超时时间到了,当前线程尚未得到锁,则会直接返回false(注意,这里并无抛出超时异常)

其实,tryLock(long time, TimeUnit unit)更像是阻塞式与非阻塞式的结合体,即在必定条件下(超时时间内,没有中断发生)阻塞,不知足这个条件则当即返回(非阻塞)。

这里把四种锁的获取方式总结以下:
锁的获取

锁的释放

相对于锁的获取,锁的释放的方法就简单的多,只有一个

void unlock();

值得注意的是,只有拥有的锁的线程才能释放锁,而且,必须显式地释放锁,这一点和离开同步代码块就自动被释放的监视器锁是不一样的。

newCondition

Lock接口还定义了一个newCondition方法:

Condition newCondition();

该方法将建立一个绑定在当前Lock对象上的Condition对象,这说明Condition对象和Lock对象是对应的,一个Lock对象能够建立多个Condition对象,它们是一个对多的关系。

Condition 接口

上面咱们说道,Lock接口中定义了newCondition方法,它返回一个关联在当前Lock对象上的Condition对象,下面咱们来看看这个Condition对象是个啥。

每个新工具的出现老是为了解决必定的问题,Condition接口的出现也不例外。
若是说Lock接口的出现是为了拓展示有的监视器锁,那么Condition接口的出现就是为了拓展同步代码块中的wait, notify机制。

监视器锁的 wait/notify 机制的弊端

一般状况下,咱们调用wait方法,主要是由于必定的条件没有知足,咱们把须要知足的事件或条件称做条件谓词。

而另外一方面,由前面几篇介绍synchronized原理的文章咱们知道,全部调用了wait方法的线程,都会在同一个监视器锁的wait set中等待,这看上去很合理,可是倒是该机制的短板所在——全部的线程都等待在同一个notify方法上(notify方法指notify()notifyAll()两个方法,下同)。每个调用wait方法的线程可能等待在不一样的条件谓词上,可是有时候即便本身等待的条件并无知足,线程也有可能被“别的线程的”notify方法唤醒,由于你们用的是同一个监视器锁。这就比如一个班上有几个重名的同窗(使用相同的监视器锁),老师喊了这个名字(notify方法),结果这几个同窗全都站起来了(等待在监视器锁上的线程都被唤醒了)。

这样以来,即便本身被唤醒后,抢到了监视器锁,发现其实条件仍是不知足,仍是得调用wait方法挂起,就致使了不少无心义的时间和CPU资源的浪费。

这一切的根源就在于咱们在调用wait方法时没有办法来指明到底是在等待什么样的条件谓词上,所以唤醒时,也不知道该唤醒谁,只能把全部的线程都唤醒了。

所以,最好的方式是,咱们在挂起时就指明了在什么样的条件谓词上挂起,同时,在等待的事件发生后,只唤醒等待在这个事件上的线程,而实现了这个思路的就是Condition接口。

有了Condition接口,咱们就能够在同一个锁上建立不一样的唤醒条件,从而在必定条件谓词知足后,有针对性的唤醒特定的线程,而不是一股脑的将全部等待的线程都唤醒。

Condition的 await/signal 机制

既然前面说了Condition接口的出现是为了拓展示有的wait/notify机制,那咱们就先来看看现有的wait/notify机制有哪些方法:

public class Object {
    public final void wait() throws InterruptedException {
        wait(0);
    }
    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException {
        // 这里省略方法的实现
    }
    public final native void notify();
    public final native void notifyAll();
}

接下来咱们再看看Condition接口有哪些方法:

public interface Condition {
    void await() throws InterruptedException;
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    
    void awaitUninterruptibly();
    boolean awaitUntil(Date deadline) throws InterruptedException;
    
    void signal();
    void signalAll();
}

对比发现,这里存在明显的对应关系:

Object 方法 Condition 方法 区别
void wait() void await()
void wait(long timeout) long awaitNanos(long nanosTimeout) 时间单位,返回值
void wait(long timeout, int nanos) boolean await(long time, TimeUnit unit) 时间单位,参数类型,返回值
void notify() void signal()
void notifyAll() void signalAll()
- void awaitUninterruptibly() Condition独有
- boolean awaitUntil(Date deadline) Condition独有

它们在接口的规范上都是差很少的,只不过wait/notify机制针对的是全部在监视器锁的wait set中的线程,而await/signal机制针对的是全部等待在该Condition上的线程。

这里多说一句,在接口的规范中,wait(long timeout)的时间单位是毫秒(milliseconds), 而awaitNanos(long nanosTimeout)的时间单位是纳秒(nanoseconds), 就这一点而言,awaitNanos这个方法名其实语义上更清晰,而且相对于wait(long timeout, int nanos)这个略显鸡肋的方法(以前的分析中咱们已经吐槽过这个方法的实现了),await(long time, TimeUnit unit)这个方法就显得更加直观和有效。

另一点值得注意的是,awaitNanos(long nanosTimeout)有返回值的,它返回了剩余等待的时间;await(long time, TimeUnit unit)也是有返回值的,若是该方法是由于超时时间到了而返回的,则该方法返回false, 不然返回true。

你们有没有觉的奇怪,一样是带超时时间的等待,为何wait方式没有返回值,await方式有返回值呢。
存在即合理,既然多加了返回值,天然是有它的用意,那么这个多加的返回值有什么用呢?

咱们知道,当一个线程从带有超时时间的wait/await方法返回时,必然是发生了如下4种状况之一:

  1. 其余线程调用了notify/signal方法,而且当前线程刚好是被选中来唤醒的那一个
  2. 其余线程调用了notifyAll/signalAll方法
  3. 其余线程中断了当前线程
  4. 超时时间到了

其中,第三条会抛出InterruptedException,是比较容易分辨的;除去这个,当wait方法返回后,咱们其实没法区分它是由于超时时间到了返回了,仍是被notify返回的。可是对于await方法,由于它是有返回值的,咱们就可以经过返回值来区分:

  • 若是awaitNanos(long nanosTimeout)的返回值大于0,说明超时时间还没到,则该返回是由signal行为致使的
  • 若是await(long time, TimeUnit unit)返回true, 说明超时时间还没到,则该返回是由signal行为致使的

源码的注释也说了,await(long time, TimeUnit unit)至关于调用awaitNanos(unit.toNanos(time)) > 0

因此,它们的返回值可以帮助咱们弄清楚方法返回的缘由

Condition接口中还有两个在Object中找不到对应的方法:

void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;

前面说的全部的wait/await方法,它们方法的签名中都抛出了InterruptedException,说明他们在等待的过程当中都是响应中断的,awaitUninterruptibly方法从名字中就能够看出,它在等待锁的过程当中是不响应中断的,因此没有InterruptedException抛出。也就是说,它会一直阻塞,直到signal/signalAll被调用。若是在这过程当中线程被中断了,它并不响应这个中断,只是在该方法返回的时候,该线程的中断标志位将是true, 调用者能够检测这个中断标志位以辅助判断在等待过程当中是否发生了中断,以此决定要不要作额外的处理。

boolean awaitUntil(Date deadline)boolean await(long time, TimeUnit unit) 其实做用是差很少的,返回值表明的含义也同样,只不过一个是相对时间,一个是绝对时间,awaitUntil方法的参数是Date,表示了一个绝对的时间,即截止日期,在这个日期以前,该方法会一直等待,除非被signal或者被中断。

至此,Lock接口和Condition接口咱们就分析完了。

咱们将在下一篇中给出Lock接口的具体实现的例子,在逐行分析AQS源码(4)——Condition接口实现中给出Condition接口具体实现的例子。

(完)

系列文章目录

相关文章
相关标签/搜索