美文网首页JAVA并发编程
线程的等待与通知,如何使用Condition实现?

线程的等待与通知,如何使用Condition实现?

作者: zhang_wq | 来源:发表于2018-07-29 16:22 被阅读0次

线程的等待与通知,目的就是为了实现线程间的协作,那一般情况下,我们最容易想到的方式是使用循环以及公共变量,比如:

public class LoopThread {
    private volatile boolean flag = true;
    public void test() {
        new Thread(() -> {
            while(flag) {
                // TODO 一直死循环,等待退出
                Thread.yield(); // 释放CPU,和JVM实现方式有关
            }
            System.out.println("我接到通知,继续执行后续操作。。。");
        }).start();

        new Thread(() -> {
            try {
                // TODO 处理任务
                System.out.println("开始处理任务。。。");
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // TODO 处理完,通知线程
            System.out.println("任务处理完成,通知线程。。。");
            flag = false;
        }).start();
    }
}

上面的代码,就是使用了循环加公共变量的方式,这种方式一定程度上能够满足需要,但是它不是最好的方式,而且循环对于cpu的占用和释放都会有相对较高的额外开销。
所以,JDK为我们提供了更为便捷的方式:wait()与notify();这两个方法并不是线程的,而是Object对象的,当然使用方式也并不像我们平时调用普通方法一样,它们的使用是有先决条件:当前线程拥有该对象监视器;我们在前面的文章提到过,关键字synchronized做线程同步,会需要拥有某些对象监视器,比如:

public class WaitAndNotify {
    /**
     * 会抛出{@link java.lang.IllegalMonitorStateException}
     */
    public void waitIllegalMonitor() {
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 会抛出{@link java.lang.IllegalMonitorStateException}
     */
    public void notifyIllegalMonitor() {
        this.notify();
    }
    public synchronized void waitTodo() {
        System.out.println("我将要进入WAITING状态");
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我被唤醒,并且我得到了内置锁");
    }
    public synchronized void notifyThread() {
        System.out.println("我将要通知任意一个线程可以继续工作");

        this.notify();

        System.out.println("通知完成,但是我暂时不释放");
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("即将释放锁资源");
    }
    public synchronized void waitTodoWithTimed() {
        System.out.println("我将要进入TIMED_WAITING状态");
        try {
            this.wait(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我被唤醒或者等待超时,并且我得到了内置锁");
    }
}

从上面代码,我们能看到,如果没有拥有对象监视器,那么直接调用对象的wait()或者notify()方法会抛出异常;代码中的waitTodo()和notifyThread()则能比较清楚的知道通知与等待的运作方式,值得注意的第一点是,当前线程即使调用了notify()方法,也不会立即释放它所拥有的共享资源,仅仅只会唤醒任意一个等待队列中等待相同共享资源的线程,被唤醒的线程依然需要竞争获取共享资源;相似的notifyAll()方法则是唤醒所有等待队列中等待相同共享资源的线程;然后被唤醒的线程如果还没有获取到共享资源,那么它会处于BLOCKED状态。从代码中可以知道wait()方法可以被中断,所以根据业务规则,我们需要做好应对处理;当然还有具有时间限制的等待,如果到时间依旧没有被唤醒,那么自动退出等待状态。
接下来我们看一下如果使用Condition实现等待与通知,在JDK1.5中新增了ReentranLock,一种更为灵活的加锁方式,可以创建多个Condition实现多路通知、选择通知;我们看一下Condition最为简单的用法:

public class ConditionWaitAndNotify {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    /**
     * 会抛出{@link java.lang.IllegalMonitorStateException}
     */
    public void waitIllegalMonitor() {
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 会抛出{@link java.lang.IllegalMonitorStateException}
     */
    public void signalIllegalMonitor() {
        condition.signal();
    }
    public void waitTodo() {
        lock.lock();
        System.out.println("我请求获取到了锁,我将要进入等待");
        try {
            condition.await();
            System.out.println("从等待状态被唤醒,并且获取到了锁资源,现在继续运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁
        }
    }
    public void signalThread() {
        lock.lock();
        try {
            System.out.println("我获取到了锁,准备要唤醒某个处于等待队列中的具有相同对象监视器的线程");
            condition.signal();
            System.out.println("唤醒了某个线程,但是我不立马释放锁");
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            System.out.println("释放锁资源");
            lock.unlock();
        }
    }
    public void waitTodoWithTimed() {
        lock.lock();
        System.out.println("我请求获取到了锁,我将要进入有时间限制的等待");
        try {
            condition.await(5, TimeUnit.SECONDS);
            System.out.println("从等待状态被唤醒或者等待超时自动唤醒,并且获取到了锁资源,现在继续运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

从上面代码中能知道,和wait()、notify()的用法比较相似,都需要获得对象监视器之后才能使用;同样Condition也有signalAll()方法,用来唤醒所有等待队列中相同对象监视器上的线程。那么就有一个问题,我现在需要指定唤醒其中一部分线程,不是一个也不是所有,这种情况下,单纯的使用上面几种方法是不可行的;这时候就可以使用Condition为我们提供的特性了:

public class ConditionWaitAndNotifyPart {
    private Lock lock = new ReentrantLock();
    private Condition conditionProducer = lock.newCondition();
    private Condition conditionOdd = lock.newCondition();
    private Condition conditionEven = lock.newCondition();
    private volatile boolean isWork = true;
    private volatile int number = 0;
    private volatile boolean hasPrint = true;

    /**
     * 主生产者
     */
    public void produce() {
        try {
            lock.lock();
            while (isWork) {
                number++;
                // 大于100停止工作
                if (number > 100) {
                    isWork = false;
                    conditionEven.signalAll();
                    conditionOdd.signalAll();
                    break;
                }

                hasPrint = false;
                if (number % 2 == 0) {
                    conditionEven.signalAll(); // 通知打印偶数的所有线程
                } else {
                    conditionOdd.signalAll(); // 通知打印奇数的所有线程
                }
                try {
                    conditionProducer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 打印奇数
     */
    public void printOdd() {
        try {
            lock.lock();
            while(isWork) {
                if (!hasPrint) {
                    System.out.println("print odd ==> " + number);
                    conditionProducer.signal();
                    hasPrint = true;
                }
                try {
                    conditionOdd.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 打印偶数
     */
    public void printEven() {
        try {
            lock.lock();
            while (isWork) {
                if (!hasPrint) {
                    System.out.println("print even ==> " + number);
                    conditionProducer.signal();
                    hasPrint = true;
                }
                try {
                    conditionEven.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            lock.unlock();
        }
    }
}

以上代码,就使用了Condition实现选择通知,我们可以根据情况不同去通知处理不同类型的线程工作,让通知等待变得更加灵活,上述代码,仅仅只能是一个生产线程,可以多个打印线程,如果需要多个生产线程,还需要进一步完善代码;在生产与消费模型中,我们可以去参考BlockingQueue的实现,它也是使用了Condition来实现的阻塞。

如果有不正确的地方,请帮忙指正,谢谢!

相关文章

  • 线程的等待与通知,如何使用Condition实现?

    线程的等待与通知,目的就是为了实现线程间的协作,那一般情况下,我们最容易想到的方式是使用循环以及公共变量,比如: ...

  • Condition

    Condition 实现管程里面的条件变量 Lock和Condition实现的管程,线程等待和通知需要调用awai...

  • 使用Condition实现等待/通知

    关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知...

  • 如何理解Condition

    在jdk1.5以后并发包中提供了Lock接口,Condition接口与Lock配合使用可以实现等待/通知模式,在此...

  • 我眼中的NSNotificationCenter

    正确的使用通知方法 通知中心与多线程 通知中心需要释放吗? 如何实现自动移除监听? 通知的实现原理 正确的使用通知...

  • 再谈ReentrantLock之2

    Condition Lock的等待通知机制,一般这么用: 等待: 通知: await 1.将线程加入到等待队列 先...

  • 2. AbstractQueuedSynchronizer(二)

    Conditon接口与示例 Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取...

  • Java的AQS详解3--Condition接口

    Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition关联的锁...

  • 线程间通信

    等待/通知机制 线程之间不是独立的的个体,他们彼此之间可以相互通信和协作。 不使用等待、通知机制实现线程间通信 两...

  • 线程锁机制

    等待/通知机制 线程之间不是独立的的个体,他们彼此之间可以相互通信和协作。 不使用等待、通知机制实现线程间通信 两...

网友评论

    本文标题:线程的等待与通知,如何使用Condition实现?

    本文链接:https://www.haomeiwen.com/subject/xxnvmftx.html