java.util.concurrent.locks.Condition 源码

Wesley13
• 阅读 683

相关类图:

java.util.concurrent.locks.Condition 源码

使用Condition和 object作为条件队列的区别:

object: 只能绑定一个内部队列,使用notify()唤醒是随机唤醒这个队列中的一个线程,而且这个线程并不是我们想要唤醒的那个线程,我们无法控制,而且那个线程可能还不一定满足唤醒的判断条件;

使用notifyAll()唤醒的是这个队列所有的线程,之后这些线程会再次判断是否符合唤醒的判断条件,此时达到了我们希望唤醒的线程成功唤醒的目的。

也是大多数时候,选用notifyAll()而不是notify()的原因。但是,notifyAll能够更容易确保类的行为的正确性,但是有时候可能会比较低效。因为将出现大量的上下文切换操作以及锁获取的竞争操作。

总而言之,object绑定的内部队列上的线程由于各自等待的条件可能有所不同,因此在调用notify/notifyAll 时进行处理的时候,显得不是那么容易处理。

Condition:可以绑定多个Condition对象到一个Lock对象实例,若在一个队列上的线程的各自等待条件也是各不相同,那么在调用signal/signalAll 的情形和object调用notify/notifyAll 的情形是一致的。但是,如果是那样去使用Condition就失去了其本身的意义,此时应当充分利用可绑定多个队列到一个Lock对象上的特性,根据等待的判断条件去决定创建多少的condition对象,从而确保在每一个队列上的线程的各自等待条件都是一样的。然后,通过 signal 就能唤醒某一个相同判断条件的队列上的一个线程,通过signalAll 可以唤醒该队列上的所有线程。从而不仅很容易确保类的行为的正确性,而且还避免出现大量的上下文切换和锁竞争操作。

Condition 具体实例:

假设使用object wait() 来控制三个线程执行顺序:线程A-->线程C-->线程B;现在A和C都处于挂起等待状态, 在执行线程B时,如果想确保唤醒的一定是A线程,此时使用object的notify 是无法满足要求的,但是Condition signal可以办到。

创建两个Condition条件队列:

ReentrantLock lock = new ReentrantLock(true);

Condition aCondition = reentrantLock.newCondition();
Condition cCondition = reentrantLock.newCondition();

    线程A: 

{
lock.lock();

while(A 挂起条件)

   aCondition.await();//此时当前线程释放lock锁,进入等待状态,等待其他线程执行aCondition.signal()时才有可能执行

A do something

lock.unlock();
}

    线程C:

{
lock.lock();

while(C 挂起条件)

   cCondition.await();//此时当前线程释放lock锁,进入等待状态,等待其他线程执行cCondition.signal()时才有可能执行

do something

lock.unlock();
}

    线程B:

{
lock.lock();

aCondition.signal();//此时当前线程释放lock锁,随机唤醒一个处于等待状态等待aCondition的线程,继续执行await后面的程序。

lock.unlock();
}

    线程B通过aCondition.signal(); 就能确保是唤醒的是等待aCondition的某一个线程,而不是等待cCondition的线程。

    完整代码如下:

package com.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    Lock lock = new ReentrantLock();
    Condition aCondition = lock.newCondition();
    Condition cCondition = lock.newCondition();

    public void aTest() {//a线程
        lock.tryLock();
        try {
            System.out.println("----aTest挂起----");
            aCondition.await();
            System.out.println("----aTest被唤醒----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println("----aTest执行完毕----");
    }

    public void cTest() {//c线程
        lock.tryLock();
        try {
            System.out.println("----cTest挂起----");
            cCondition.await();
            System.out.println("----cTest被唤醒----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println("----cTest执行完毕----");
    }

    public void bTest() {//main线程作为b线程
        try {
            lock.tryLock();
            System.out.println("bTest唤醒aTest");
            aCondition.signal();//唤醒同样需要持有相同的lock对象锁
        } finally {
            lock.unlock();//不释放锁,则a线程始终无法继续执行。
        }

        try {
            System.out.println("释放锁,让aTest继续执行");
            Thread.sleep(1000);//让a线程竞争到锁
            lock.tryLock();//a线程执行完毕,此时将继续向下执行唤醒c线程操作
            System.out.println("bTest唤醒cTest");
            cCondition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockTest lockTest = new LockTest();
        new ALockThread(lockTest).start();
        new CLockThread(lockTest).start();
        Thread.sleep(1000);//确保a和c线程都已启动
        lockTest.bTest();
    }
}


package com.thread;

public class ALockThread extends Thread{
    private LockTest lockTest;

    ALockThread(LockTest lockTest){
        this.lockTest=lockTest;
    }

    @Override
    public void run() {
        lockTest.aTest();
    }
}

package com.thread;

public class CLockThread extends Thread{
    private LockTest lockTest;

    CLockThread(LockTest lockTest){
        this.lockTest=lockTest;
    }

    @Override
    public void run() {
        lockTest.cTest();
    }
}

    运行结果为:

----aTest挂起----
----cTest挂起----
bTest唤醒aTest
释放锁,让aTest继续执行
----aTest被唤醒----
----aTest执行完毕----
bTest唤醒cTest
----cTest被唤醒----
----cTest执行完毕----

    需要注意的是,此处的signal() 换成 signalAll 的运行结果不变,不变的原因上面已经进行了说明。

java.util.concurrent.locks.Condition 源码:

package java.util.concurrent.locks;

import java.util.concurrent.TimeUnit;
import java.util.Date;

public interface Condition {

    void await() throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

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

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

接口 Condition

所有已知实现类:

AbstractQueuedLongSynchronizer.ConditionObject

AbstractQueuedSynchronizer.ConditionObject

Condition 通过与任意 Lock 实现组合使用,为每个对象提供多个wait-set。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法(waitnotify 和 notifyAll)的使用。

条件(也称为_条件队列_ 或_条件变量_)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用 newCondition() 方法。

示例:

假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

Condition 实现可以提供不同于 Object 监视器方法的行为和语义,比如受保证的通知排序,或者在执行通知时不需要保持一个锁。如果某个实现提供了这样特殊的语义,则该实现必须记录这些语义。

注意,Condition 实例只是一些普通的对象,它们自身可以用作 synchronized 语句中的目标,并且可以调用自己的 wait 和 notification 监视器方法。为了避免混淆,建议除了在其自身的实现中之外,切勿在 synchronized 语句使用 Condition 实例作为锁对象。

void

**await**() 
          造成当前线程在接到信号或被中断之前一直处于等待状态。

boolean

**[await](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#await(long,%20java.util.concurrent.TimeUnit))**(long time, [TimeUnit](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/TimeUnit.html) unit) 
          造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

long

**awaitNanos**(long nanosTimeout) 
          造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

void

**awaitUninterruptibly**() 
          造成当前线程在接到信号之前一直处于等待状态。

boolean

**awaitUntil**([Date](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/Date.html) deadline) 
          造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。

void

**[signal](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#signal())**() 
          唤醒一个等待线程。

void

**[signalAll](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/locks/Condition.html#signalAll())**() 
          唤醒所有等待线程。

await

void await() throws InterruptedException

    造成当前线程在接到信号或被 中断之前一直处于等待状态。

    与此 Condition 相关的锁将以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;
  • 其他某个线程调用此 Condition 的 signalAll() 方法;
  • 其他某个线程中断当前线程,且当前线程支持中断线程的挂起;则抛出 InterruptedException,并清除当前线程的中断状态。

    在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。

抛出:

[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html) - 如果当前线程被中断(并且支持中断线程挂起)

awaitUninterruptibly

void awaitUninterruptibly()

    造成当前线程在接到信号之前一直处于等待状态。

    与此条件相关的锁以将原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;
  • 其他某个线程调用此 Condition 的 signalAll() 方法;

    在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。

    如果在进入此方法时设置了当前线程的中断状态,或者在等待时,线程被中断,那么在接到信号之前,它将继续等待。当最终从此方法返回时,仍然将其设置为中断状态。

awaitNanos

long awaitNanos(long nanosTimeout) throws InterruptedException

    造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

    与此条件相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;
  • 其他某个线程调用此 Condition 的 signalAll() 方法;
  • 其他某个线程中断当前线程,且支持中断线程的挂起;则抛出 InterruptedException,并且清除当前线程的已中断状态。
  • 已超过指定的等待时间;

    在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。

    在返回时,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanosTimeout 值的时间,如果超时,则返回一个小于等于 0 的值。可以用此值来确定在等待返回但某一等待条件仍不具备的情况下,是否要再次等待,以及再次等待的时间。此方法的典型用法采用以下形式:

synchronized boolean aMethod(long timeout, TimeUnit unit) { long nanosTimeout = unit.toNanos(timeout); while (!conditionBeingWaitedFor) { if (nanosTimeout > 0) nanosTimeout = theCondition.awaitNanos(nanosTimeout); else return false; } // ... }

    设计注意事项:此方法需要一个 nanosecond 参数,以避免在报告剩余时间时出现截断错误。在发生重新等待时,这种精度损失使得程序员难以确保总的等待时间不少于指定等待时间。

参数:

nanosTimeout - 等待的最长时间,以毫微秒为单位

返回:

nanosTimeout 值减去花费在等待此方法的返回结果的时间的估算。正值可以用作对此方法进行后续调用的参数,来完成等待所需时间结束。小于等于零的值表示没有剩余时间。

抛出:

[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html) - 如果当前线程被中断(并且支持中断线程挂起)

await

boolean await(long time, TimeUnit unit) throws InterruptedException

    造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: 

    awaitNanos(unit.toNanos(time)) > 0

参数:

time - 最长等待时间

unit - time 参数的时间单位

返回:

    如果在从此方法返回前检测到等待时间超时,则返回 false,否则返回 true

抛出:

[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html) - 如果当前线程被中断(并且支持中断线程挂起)

awaitUntil

boolean awaitUntil(Date deadline) throws InterruptedException

    造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。

    与此条件相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下情况_之一_ 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;
  • 其他某个线程调用此 Condition 的 signalAll() 方法;
  • 其他某个线程中断当前线程,且支持中断线程的挂起;则抛出 InterruptedException,并且清除当前线程的已中断状态。
  • 指定的最后期限到了;

    在所有情况下,当前线程唤醒重新执行,都必须重新获取与此条件有关的锁。

    返回值指示是否到达最后期限,使用方式如下:

synchronized boolean aMethod(Date deadline) { boolean stillWaiting = true; while (!conditionBeingWaitedFor) { if (stillWaiting) stillWaiting = theCondition.awaitUntil(deadline); else return false; } // ... }

参数:

deadline - 一直处于等待状态的绝对时间

返回:

    如果在返回时已经到达最后期限,则返回 false,否则返回 true

抛出:

[InterruptedException](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/InterruptedException.html) - 如果当前线程被中断(并且支持中断线程挂起)

signal

void signal()

    唤醒一个等待线程。

    如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

signalAll

void signalAll()

    唤醒所有等待线程。

    如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这