Lock和Condition原来是这么配对使用的

LockSupport 与 Condition

LockSupport

定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组建的基础工具.
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark方法来唤醒一个被阻塞的线程.这些方法及描述如下表所示:

jdk1.6中,LockSupport增加了park(Object blocker)、parkNanos(Object blocker,long nanos)和parkUntil(Object blocker,long deadline)3个方法,用于阻塞当前线程.其中blocker主要用来标识当前线程在等待的对象(也就是阻塞对象).–主要是为了方便问题排查和系统监控.

Condition

任何Java对象,都有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()和notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式.Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式.
不过这两者在使用方式以及功能特性上还是有差别的,具体看下表对比

Condition接口与示例

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁.Condition对象是由Lock对象创建出来的,换句话说,Condition时依赖Lock对象的.
使用方式如下所示:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
    lock.lock();
    try{
        condition.await();
    }finally{
        lock.unlock();
    }
}

public void conditionSignal() throws InterruptedException{
    lock.lock();
    try{
        condition.signal();
    }finally{
        lock.unlock();
    }
}

当调用await()方法后,一般会将当前线程释放锁并在此等待,而其他线程调用Condition对象的signal(),通知当前线程后,当前线程才从await()方法返回,并且在返回前就已经获取了锁.

Condition的实现分析

ConditionObject是同步器AQS的内部类,因为Condition的操作需要获取到相关的锁.每个Condition对象都包含一个队列-等待队列,该队列是Condition实现等待通知的关键.

1、等待队列

等待队列是一个FIFO的队列,队列中的每个节点都包含了一个线程的引用,该线程就是Condition对象上等待的线程,如果一个线程调用了Condtion.await()方法,那么该线程将会释放锁、构造节点,并加入等待队列并进入等待状态.
这个和同步器的节点定义是一样的,也就是说,同步队列和等待队列中的节点类型用的都是同步器的静态内部类.
一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾结点(lastWaiter).当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列基本结构如图所示

Condition拥有收尾节点的使用,而新增节点只需要将原有的尾结点的nextWaiter指向他,并切更新尾节点即可.这个节点更新的过程并没有使用CAS来保证,因为调用await()方法的线程必定已经获取到锁了,也就是该过程是由锁来保证线程安全的.
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列,其关系如下所示:

Condition的实现是同步器的内部类,因此每个Condition实例都能访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用.

2、等待

调用Condition的await(),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态.当从await()方法返回时,当前线程一定获取了Condition相关的锁.
如果从队列的角度看(等待队列和同步队列)的角度看await()方法,当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中.

public final void await() throws InterruptedException{
    if(Thread.interrupted())
        throw new InterruptedException();
    // 当前线程加入等待队列
    Node node = addConditionWaiter();
    // 释放同步状态,也就是释放锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 判断是否在同步队列中
    while(!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 退出while循环后,加入到获取同步状态的竞争中
    if(acquireQueued(node,savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if(node.nextWaiter != null)
        unlinkCancelledWaiters();
    if(interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态.
当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态.如果不是通过其他线程调用Contition.signal()方法唤醒的,而是通过对等待线程进行终端,则会抛出InterruptedException.
如果是从队列的角度看,当前线程加入Condition的等待队列并不会直接加入,而是通过addConditionWaiter()方法把当前线程构造成一个新的节点并将其加入等待队列.

3、通知

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移动到同步队列中.
代码如下:

public  final void signal(){
    // 当前线程必须是获取了锁的线程
    if(!isHeldExclusive())
        throw new IllegalMonitorStateException();
    // 获取等待队列的首节点
    Node first = firstWaiter;
    if(first != null)
        // 将其移动到同步队列,并使用LockSupport唤醒节点中的线程.
        doSignal(first);
}

节点从等待队列移动到同步队列的过程

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中.
成功获取同步状态之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功的获取了锁.
Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有的节点全部移动到同步队列中,并唤醒每个节点的线程.

总结

jdk既然提供了monitor对象和synchronized关键字配合wait、notify、notifyAll方法实现等待/通知模式,为什么还要提供Lock和Condition配合await、signal、signalAll来实现等待/通知模式

  1. Lock可以显性的获取和释放锁,synchronized是隐性的.
  2. Lock支持手动获取锁,释放锁,我们可以自定义我们需要的锁,比如可重入锁,读写锁,具备更强的可拓展性.
  3. condition支持多个等待队列即多个条件变量或者锁投票,支持可中断等候,支持时间锁等候、无块结构锁.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值