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

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

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实现?)