6.线程系列- JUC中的Condition对象

Condition使用简介

任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Objetc的几个方法,比如wait(),notify(),notifyAll()几个方法实现等待或通知机制,同样的,在java Lock体系下依然会有同样的方法实现等待/通知机制。

从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。两者除了在使用方式上不同外,在功能特性上还是有很多的不同:

  1. Condition能够支持不响应中断,而通过使用Object方式不支持
  2. Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个
  3. Condition能够支持超时时间的设置,而Object不支持

Condition由ReentrantLock对象创建,并且可以同时创建多个,Condition接口在使用前必须先调用ReentrantLock的lock()方法获得锁,之后调用Condition接口的await()将释放锁,并且在该Condition上等待,直到有其他线程调用Condition的signal()方法唤醒线程,使用方式和wait()、notify()类似。

案例

public class ThreadDemo6 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static class T1 extends Thread{
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + "," + this.getName() + "准备获取锁!");
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + this.getName() + "获取锁成功!");
                condition.await();
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            System.out.println(System.currentTimeMillis() + "," + this.getName() + "释放锁成功!");
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + "," + this.getName() + "准备获取锁!");
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + this.getName() + "获取锁成功!");
                condition.signal();
                System.out.println(System.currentTimeMillis() + "," + this.getName() + " signal!");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + this.getName() + "准备释放锁!");
            }finally {
                lock.unlock();
            }
            System.out.println(System.currentTimeMillis() + "," + this.getName() + "释放锁成功!");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("t1");
        t1.start();
        TimeUnit.SECONDS.sleep(5);
        T2 t2 = new T2();
        t2.setName("t2");
        t2.start();
    }
}

执行结果:

1604540292533,t1准备获取锁!
1604540292533,t1获取锁成功!
1604540297543,t2准备获取锁!
1604540297543,t2获取锁成功!
1604540297543,t2 signal!
1604540302544,t2准备释放锁!
1604540302544,t2释放锁成功!
1604540302544,t1释放锁成功!

Condition.await()方法和Object.wait()方法类似,当使用Condition.await()方法时,需要先获取Condition对象关联的ReentrantLock的锁,在Condition.await()方法被调用时,当前线程会释放这个锁,并且当前线程会进行等待(处于阻塞状态)。在signal()方法被调用后,系统会从Condition对象的等待队列中唤醒一个线程,一旦线程被唤醒,被唤醒的线程会尝试重新获取锁,一旦获取成功,就可以继续执行了。因此,在signal被调用后,一般需要释放相关的锁,让给其他被唤醒的线程,让他可以继续执行。

Condition常用方法

和Object中wait类似的方法

  1. void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;
  2. long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时;
  3. boolean await(long time, TimeUnit unit) throws InterruptedException:同第二种,支持自定义时间单位,false:表示方法超时之后自动返回的,true:表示等待还未超时时,await方法就返回了(超时之前,被其他线程唤醒了)
  4. boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间
  5. void awaitUninterruptibly();:当前线程进入等待状态,不会响应线程中断操作,只能通过唤醒的方式让线程继续

和Object的notify/notifyAll类似的方法

  1. void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
  2. void signalAll():与1的区别在于能够唤醒所有等待在condition上的线程

Condition.await()过程中被打断

public class ThreadDemo7 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static class T1 extends Thread{
        @Override
        public void run() {
            lock.lock();
            try {
                condition.await();
            }catch (InterruptedException e){
                e.printStackTrace();
                System.out.println("中断标志:" + this.isInterrupted());
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("t1");
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        //给t1线程发送中断信号
        System.out.println("1、t1中断标志:" + t1.isInterrupted());
        t1.interrupt();
        System.out.println("2、t1中断标志:" + t1.isInterrupted());
    }
}

运行结果:

1、t1中断标志:false
2、t1中断标志:true
中断标志:false
java.lang.InterruptedException
Disconnected from the target VM, address: '127.0.0.1:58247', transport: 'socket'
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at com.example.thread.ThreadDemo7$T1.run(ThreadDemo7.java:18)

调用condition.await()之后,线程进入阻塞中,调用t1.interrupt(),给t1线程发送中断信号,await()方法内部会检测到线程中断信号,然后触发 InterruptedException异常,线程中断标志被清除。从输出结果中可以看出,线程t1中断标志的变换过程:false->true->false

await(long time, TimeUnit unit)超时之后自动返回

public class ThreadDemo8 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static class T1 extends Thread{
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
                boolean await = condition.await(2, TimeUnit.SECONDS);
                System.out.println(await);
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
            }catch (InterruptedException e){
                e.printStackTrace();
                System.out.println("中断标志:" + this.isInterrupted());
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("t1");
        t1.start();
    }
}

运行结果:

1604541240076,t1,start
false
1604541242079,t1,end

t1线程等待2秒之后,自动返回继续执行,最后await方法返回false,await返回false表示超时之后自动返回

await(long time, TimeUnit unit)超时之前被唤醒

public class ThreadDemo9 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static class T1 extends Thread{
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
                boolean await = condition.await(5, TimeUnit.SECONDS);
                System.out.println(await);
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
            }catch (InterruptedException e){
                e.printStackTrace();
                System.out.println("中断标志:" + this.isInterrupted());
            }finally {
                lock.unlock();
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run() {
            lock.lock();
            try {
                condition.signal();
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("t1");
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        T2 t2 = new T2();
        t2.setName("t2");
        t2.start();
    }
}

运行结果:

1604541402399,t1,start
true
1604541404408,t1,end

t1线程中调用 condition.await(5,TimeUnit.SECONDS);方法会释放锁,等待5秒,主线程休眠2秒,然后线程t2启动,获取锁,之后调用signal()方法唤醒t1,输出结果中发现await后过了2秒(1、3行输出结果的时间差),await方法就返回了,并且返回值是true。true表示await方法超时之前被其他线程唤醒了。

long awaitNanos(long nanosTimeout)超时返回

public class ThreadDemo10 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static class T1 extends Thread{
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
                long l = condition.awaitNanos(TimeUnit.SECONDS.toNanos(5));
                System.out.println(l);
                System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("t1");
        t1.start();
//        TimeUnit.SECONDS.sleep(1);
//        try {
//            lock.lock();
//            condition.signal();
//        }finally {
//            lock.unlock();
//        }
    }
}

运行结果:

1604542251660,t1,start
-129800
1604542256662,t1,end

awaitNanos参数为纳秒,可以调用TimeUnit中的一些方法将时间转换为纳秒。

t1调用await方法等待5秒超时返回,返回结果为负数,表示超时之后返回的。

waitNanos(long nanosTimeout)超时之前被唤醒

把上面案例注释的部分打开即可,运行之后:

1604542330740,t1,start
3999786400
1604542331742,t1,end

t1中调用await休眠5秒,主线程休眠1秒之后,调用signal()唤醒线程t1,await方法返回正数,表示返回时距离超时时间还有多久,将近4秒,返回正数表示,线程在超时之前被唤醒了。

其他几个有参的await方法和无参的await方法一样,线程调用interrupt()方法时,这些方法都会触发InterruptedException异常,并且线程的中断标志会被清除。

同一个锁支持创建多个Condition

public class BlockingQueueDemo<E> {
    //队列的最大容量
    int size;
    //队列
    LinkedList<E> list = new LinkedList<>();
    Lock lock = new ReentrantLock();
    //满队列的等待条件
    Condition fullCond = lock.newCondition();
    //空队列的等待条件
    Condition emptyCond = lock.newCondition();

    BlockingQueueDemo(int size) {
        this.size = size;
    }

    //入队操作
    public void enqueue(E e) throws InterruptedException {
        lock.lock();
        try {
            //队列已满,等待
            while (list.size() == size) {
                fullCond.await();
            }
            //入队:加入链表末尾
            list.add(e);
            System.out.println("入队:" + e);
            //通知在emptyCond条件上等待的线程
            emptyCond.signal();
        } finally {
            lock.unlock();
        }
    }

    //出队操作
    public E dequeue() throws InterruptedException {
        E e;
        lock.lock();
        try {
            //队列为空,在emptyCond条件上等待
            while (0 == list.size()){
                emptyCond.await();
            }
            //出队:移除链表首元素
            e = list.removeFirst();
            System.out.println("出队:" + e);
            //通知在fullCond条件上等待的线程
            fullCond.signal();
            return e;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        BlockingQueueDemo<Integer> queueDemo = new BlockingQueueDemo<>(2);
        for (int i = 0; i < 10; i++) {
            int data = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        queueDemo.enqueue(data);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Integer data = queueDemo.dequeue();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

}

代码非常容易理解,创建了一个阻塞队列,大小为2,队列满的时候,会被阻塞,等待其他线程去消费,队列中的元素被消费之后,会唤醒生产者,生产数据进入队列。上面代码将队列大小置为1,可以实现同步阻塞队列,生产1个元素之后,生产者会被阻塞,待消费者消费队列中的元素之后,生产者才能继续工作。

Object的监视器方法与Condition接口的对比

在这里插入图片描述

总结

  1. 使用condition的步骤:创建condition对象,获取锁,然后调用condition的方法
  2. 一个ReentrantLock支持床多个condition对象
  3. void await() throws InterruptedException;方法会释放锁,让当前线程等待,支持唤醒,支持线程中断
  4. void awaitUninterruptibly();方法会释放锁,让当前线程等待,支持唤醒,不支持线程中断
  5. long awaitNanos(longnanosTimeout) throws InterruptedException;参数为纳秒,此方法会释放锁,让当前线程等待,支持唤醒,支持中断。超时之后返回的,结果为负数;超时之前被唤醒返回的,结果为正数(表示返回时距离超时时间相差的纳秒数)
  6. boolean await(longtime,TimeUnitunit) throws InterruptedException;方法会释放锁,让当前线程等待,支持唤醒,支持中断。超时之后返回的,结果为false;超时之前被唤醒返回的,结果为true
  7. boolean awaitUntil(Datedeadline) throws InterruptedException;参数表示超时的截止时间点,方法会释放锁,让当前线程等待,支持唤醒,支持中断。超时之后返回的,结果为false;超时之前被唤醒返回的,结果为true
  8. void signal();会唤醒一个等待中的线程,然后被唤醒的线程会被加入同步队列,去尝试获取锁
  9. void signalAll();会唤醒所有等待中的线程,将所有等待中的线程加入同步队列,然后去尝试获取锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值