【AQS】LockSupport工具、Condotion接口

【AQS】LockSupport工具、Condotion接口

LockSupport:

​ 有一组静态方法,给AQS提供了最基本的阻塞和唤醒功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jgj9xKT-1632300962882)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922132215541.png)]

方法描述:

​ park有停车意思,假设当前线程为车辆,阻塞就是停车。

  1. park() 阻塞当前线程,如果调用upoark()或者当前线程被中断才能从park()返回

  2. parkNanos(long) 超时返回,参数是纳秒,在park基础上增加了时限

  3. parkUntil(long deadline) 参数是(从1970年开始到当前时间的毫秒数)

     public final boolean awaitUntil(Date deadline)
          long abstime = deadline.getTime();//返回从1970开始到deadline日期的毫秒数
    		 LockSupport.parkUntil(this, abstime);
    
    1. unpark(Thread) 唤醒线程

在java6中,新增了park(Object)、parkNanos(Object,long)、parkUntil(Object,long),其中Object对象用来标识当前线程在等待的对象,该对象主要用来问题排查和系统监控

Condition

​ 多线程基础时有学过,每个Java对象都有一组监视器方法,定义在Object超类里面。主要包括,wait(),wait(long timeout),notify(),notifyAll(),配合synchronized可以时现等待/通知模式。

Object的等待/通知模式

package com.w.juc;

public class WaitAndNotifyDemo {
    volatile static int products =0;
    private static Object monitor =new Object();

    public static void producter() throws InterruptedException {
        synchronized(monitor) {
            while(true){
                if (products == 0){
                    products++;
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread()+"生产了一个产品,通知消费者消费,现有产品"+products);
                    monitor.notifyAll();
                }else{
                    System.out.println(Thread.currentThread()+"产品还未消费,等待消费");
                    monitor.wait();
                }
            }

        }
    }

    public static void consumer() throws InterruptedException {
        synchronized (monitor) {
            while(true){
                if (products>0){
                    Thread.sleep(1000);
                    products--;
                    System.out.println(Thread.currentThread()+"消费者消费一个产品,通知生产者再次生产,现有产品"+products);
                    monitor.notifyAll();
                }else {
                    System.out.println(Thread.currentThread()+"还没有产品,等待生成者生产");
                    monitor.wait();
                }
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
      new Thread(()-> {
          try {
              producter();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      },"【生产者】").start();
        new Thread(()-> {
            try {
                consumer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"【消费者】").start();

    }

}

这个是我自己写的,按照范例

  1. 等待方

    1. 获取对象的锁
    2. 如果条件不满足就等待,被通知后仍要检查条件
    3. 条件满足执行
  2. 通知方

    1. 获取对象的锁
    2. 改变条件
    3. 通知等待的锁

我写的生产者和消费者同时都是等待也是通知方。

生产者先生产一个产品,去通知消费者消费,然后马上再次进入while,此时已经有了产品就会去等待消费者消费

消费者消费完一个产品,马上进入while,此时已经无产品,通知消费者就唤醒了等待中的生产者去生产,然后自己被阻塞等待产品

Condition等待/通知模式

Condition也提供了类似Object的监视器方法,与Lock配合可实现等待/通知模式。

实现示例

通过实现一个有界队列来使用Condition。有界队列:当队列为空时,将当前线程阻塞到空等待队列。当队列满了,将当前线程阻塞到满等待队列

package com.w.juc;

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

/**
 * 实现有界队列
 *  一个固定大小数组
 */
public class ConditionDemo<T> {

    private ReentrantLock reentrantLock = new ReentrantLock();
    private Condition notFull = reentrantLock.newCondition();
    private Condition notEmpty = reentrantLock.newCondition();
    private int addIndex,removeIndex,count;
    private Object[] items;
    public ConditionDemo(int size){
        items = new Object[size];
    }

    public void add(T t){
        reentrantLock.lock();
        try {
            while (count == items.length){
                System.out.println(Thread.currentThread()+"想放但满了,不能放了,进入满等待队列");
                notFull.await();
            }
            items[addIndex++] = t;
            if (addIndex == items.length){
                addIndex = 0;
            }
            count++;
            System.out.println(Thread.currentThread()+"增加成功,当前数组元素个数"+count+"唤醒想删元素的线程");
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }

    }

    public void remove(){
        reentrantLock.lock();
        try {
            while (count == 0){
                System.out.println(Thread.currentThread()+"想删但是空,不能删了,进入空等待队列");
                notEmpty.await();
            }
            items[removeIndex++] = null;
            if (removeIndex == items.length){
                removeIndex = 0;
            }
            count--;
            System.out.println(Thread.currentThread()+"删除成功,当前数组元素个数"+count+"唤醒想增加元素的线程");
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionDemo conditionDemo = new ConditionDemo(2);
        new Thread(()->conditionDemo.add("123"),"【Thread1 add】").start();
        new Thread(()->conditionDemo.add("123"),"【Thread2 add】").start();
        new Thread(()->conditionDemo.add("123"),"【Thread3 add】").start();
        new Thread(()->conditionDemo.remove(),"【Thread4 remove】").start();
        new Thread(()->conditionDemo.remove(),"【Thread5 remove】").start();
        new Thread(()->conditionDemo.remove(),"【Thread6 remove】").start();
        new Thread(()->conditionDemo.remove(),"【Thread7 remove】").start();
        new Thread(()->conditionDemo.add("123"),"【Thread8 add】").start();
    }


}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9v4hf56-1632300962887)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922154327817.png)]

使用此有界队列:

​ 初始化一个大于为2的有界队列,先通过三个线程增加三个元素,前两个线程添加正常,第三个线程被阻塞

删除一个元素后,第三个添加线程被唤醒,添加元素。

当前元素个数为2,之后连续三个删线程,第三个删线程被阻塞。

当第7个添加线程添加元素后唤醒等待的第7个删线程,删除元素成功,当前元素为0

实现分析

ConditionObject是同步器AQS的内部实现类,每个Condition都包含着一个FIFO单向队列(等待队列)

ConditionObject的主要实现:等待队列,等待和通知。

Condition(ConditionObject) 由lock 通过newCondition创建,使用时必须获取到锁

  1. 等待队列(conditon queue 也叫条件队列)

    等待队列中存放的是,当前线程获取到锁之后,调用了condition.await()方法。会把当前线程构造成一个节点加入到等待队列中,而这个节点则是复用了同步队列中的节点,也就是Node。Condition使用lastWaiter和firstWaiter来记录首尾节点,在Node中复用了nextWaiter用作指向下一下节点的索引

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dTrHgfAM-1632300962888)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922161650525.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-to0Ss1dm-1632300962890)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922161944362.png)]

新增节点只需要将新节点添加到队列尾部,并让lastWaiter指向它就行,不需要使用CAS更新。因为此时当前调用await()方法的线程必然获取到了锁

在Object的监视器模式上,有一个同步队列和一个等待队列。而Condition则是有一个同步队列和多个等待队列

2. 等待

​ 当前线程获取锁,然后调用等待awaite(),会将当前线程构建成一个节点,加入到等待队列,线程状态变为等待状态同时释放锁

从队列角度看awaite方法。当调用awaite方法,相当于将首节点移出然后构建一个新的节点放入等待队列队尾。

调用awaite方法,会先构建一个节点加入等待队列,释放同步状态(锁),唤醒同步队列中的后继节点,然后当前线程进入等待状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTojPP9h-1632300962891)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922164350621.png)]

如图,当前线程加入到等待队列。同步器的首节点并不会直接移动到等待队列,而是新构建一个节点

3.通知

​ 调用Condition的Sigal方法,会将等待队列中时间最长的节点唤醒,加入到同步队列队尾,通过同步器的acquireQueued方法加入到获取同步状态的竞争

成功获取到同步状态,被唤醒的线程从awaite()方法中返回,表示当前线程成功获取到了锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZNLOxod-1632300962893)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922165231647.png)]

如图,是唤醒之后节点从等待队列到了同步队列过程

Condition等待通知本质

当一个线程持有锁,调用awaite()方法:

1. 以当前线程为信息构建一个新Node节点加入等待队列队尾
2. 释放锁,真实最后调用的是Lock本身重写的tryRelease,会唤醒后继节点
3. while自旋,直到被移到了同步队列,就是被signal()
4. 阻塞,调用的是LockSupport的park(Object)方法。

当一个线程持有锁,调用signal()方法:

1. 首先判断当前线程获取到了锁没
2. 将当前线程的状态改为0,初始状态
3. 将当前线程唤醒并放到同步队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jOfhphmU-1632311665686)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210922190018557.png)]

final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
    //如果当前节点状态为CONDITION,则将状态改为0准备加入同步队列;如果当前状态不为CONDITION,说明该节点等待已被中断,则该方法返回false,doSignal()方法会继续尝试唤醒当前节点的后继节点
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);//将节点加入同步队列,返回的p是节点在同步队列中的先驱节点
        int ws = p.waitStatus;
    	//如果先驱节点的状态为CANCELLED(>0) 或设置先驱节点的状态为SIGNAL失败,那么就立即唤醒当前节点对应的线程
    	//ws>0代表前驱节点状态已经为CANCELLED(1),可以立即唤醒线程尝试获取同步状态
    	//cas设置成功,则并不需要现在唤醒,因为等到前驱节点释放后还会再次唤醒线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
  1. 唤醒/或者不唤醒的放到同步队列
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值