欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-08 10:32:22
...

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

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来实现的阻塞。

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

上一篇:

下一篇: