• java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)


    转载自https://www.cnblogs.com/jalja/p/5895051.html

    一、Condition 类

      在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.concurrent.locks.ReentrantLock 锁,JDK也为我们提供了与此功能相应的类java.util.concurrent.locks.Condition。Condition与重入锁是通过lock.newCondition()方法产生一个与当前重入锁绑定的Condtion实例,我们通知该实例来控制线程的等待与通知。该接口的所有方法:

    复制代码
    public interface Condition {
         //使当前线程加入 await() 等待队列中,并释放当锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似。
        void await() throws InterruptedException;
    
        //调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
        //调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。 
        void awaitUninterruptibly();
    
        // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
        //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;
        //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。 
        long awaitNanos(long nanosTimeout) throws InterruptedException;
    
        //与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。
        boolean await(long time, TimeUnit unit) throws InterruptedException;
    
       //适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。
        boolean awaitUntil(Date deadline) throws InterruptedException;
        
        //唤醒一个在 await()等待队列中的线程。与Object.notify()相似
        void signal();
    
       //唤醒 await()等待队列中所有的线程。与object.notifyAll()相似
        void signalAll();
    }
    复制代码

    二、使用

    1、await()  等待  与 singnal()通知

    复制代码
     1 package com.jalja.org.base.Thread;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 import java.util.concurrent.locks.Condition;
     5 import java.util.concurrent.locks.ReentrantLock;
     6 
     7 /**
     8  * Condition 配合Lock  实现线程的等待 与通知
     9  */
    10 public class ConditionTest{
    11     public static ReentrantLock lock=new ReentrantLock();
    12     public static Condition condition =lock.newCondition();
    13     public static void main(String[] args) {
    14         new Thread(){
    15             @Override
    16             public void run() {
    17                 lock.lock();//请求锁
    18                 try{
    19                     System.out.println(Thread.currentThread().getName()+"==》进入等待");
    20                     condition.await();//设置当前线程进入等待
    21                 }catch (InterruptedException e) {
    22                     e.printStackTrace();
    23                 }finally{
    24                     lock.unlock();//释放锁
    25                 }
    26                 System.out.println(Thread.currentThread().getName()+"==》继续执行");
    27             }    
    28         }.start();
    29         new Thread(){
    30             @Override
    31             public void run() {
    32                 lock.lock();//请求锁
    33                 try{
    34                     System.out.println(Thread.currentThread().getName()+"=》进入");
    35                     Thread.sleep(2000);//休息2秒
    36                     condition.signal();//随机唤醒等待队列中的一个线程
    37                     System.out.println(Thread.currentThread().getName()+"休息结束");
    38                 }catch (InterruptedException e) {
    39                     e.printStackTrace();
    40                 }finally{
    41                     lock.unlock();//释放锁
    42                 }
    43             }    
    44         }.start();
    45     }
    46 }
    复制代码

    执行结果:

    Thread-0==》进入等待
    Thread-1=》进入
    Thread-1休息结束
    Thread-0==》继续执行

    流程:在调用await()方法前线程必须获得重入锁(第17行代码),调用await()方法后线程会释放当前占用的锁。同理在调用signal()方法时当前线程也必须获得相应重入锁(代码32行),调用signal()方法后系统会从condition.await()等待队列中唤醒一个线程。当线程被唤醒后,它就会尝试重新获得与之绑定的重入锁,一旦获取成功将继续执行。所以调用signal()方法后一定要释放当前占用的锁(代码41行),这样被唤醒的线程才能有获得锁的机会,才能继续执行。

    三、JDK中对Condition 的使用

      我们来看看java.util.concurrent.ArrayBlockingQueue;

      基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

      看看他的put方法:

    复制代码
      public void put(E e) throws InterruptedException {
            checkNotNull(e);//对传入元素的null判断
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();//对put()方法做同步
            try {
                while (count == items.length)//如果队列已满
                    notFull.await();//让当前添加元素的线程进入等待状态
                insert(e);// 如果有其他线程调用signal() 通知该线程 ,则进行添加行为
            } finally {
                lock.unlock();//释放锁
            }
        }
        
        private E extract() {
            final Object[] items = this.items;
            E x = this.<E>cast(items[takeIndex]);
            items[takeIndex] = null;
            takeIndex = inc(takeIndex);
            --count;
            notFull.signal();//唤醒一个在Condition等待队列中的线程
            return x;
        }
    复制代码
    每天用心记录一点点。内容也许不重要,但习惯很重要!
     
     
     
     

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiq408694711/article/details/51052592


    是否释放锁:调用sleep和yield的时候不释放当前线程所获得的锁,但是调用await/wait的时候却释放了其获取的锁并阻塞等待。

    调用后何时恢复:

    # sleep让线程阻塞,且在指定的时间之内都不会执行,时间到了之后恢复到就绪状态,也不一定被立即调度执行;

    # yield只是让当前对象回到就绪状态,还是有可能马上被再次被调用执行。

    # await/wait,它会一直阻塞在条件队列之上,之后某个线程调用对应的notify/signal方法,才会使得await/wait的线程回到就绪状态,也是不一定立即执行。

    谁的方法:yield和sleep方法都是Thread类的,而wait方法是Object类的,await方法是Condition显示条件队列的。

    执行环境:yield和sleep方法可以放在线程中的任意位置,而await/wait方法必须放在同步块里面,否则会产生运行时异常。

    await/wait

    Sleep

    Yield

    是否释放持有的锁

    释放

    不释放

    不释放

    调用后何时恢复

    唤醒后进入就绪态

    指定时间后

    立刻进入就绪态

    谁的方法

    Condition/Object

    Thread

    Thread

    执行环境

    同步代码块

    任意位置

    任意位置
    ---------------------
    作者:小弟季义钦
    来源:CSDN
    原文:https://blog.csdn.net/jiyiqinlovexx/article/details/51052592
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    asp.net session对象的持久化
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    小白也能看懂的约瑟夫环问题
  • 原文地址:https://www.cnblogs.com/heroinss/p/9917796.html
Copyright © 2020-2023  润新知