Java并发编程那些事儿(四)——线程间的协作

611 阅读5分钟

原创:花括号MC(微信公众号:huakuohao-mc)。关注JAVA基础编程及大数据,注重经验分享及个人成长。

这是并发编程系列的第四篇文章。上一篇介绍的是通过ThreadLocal的方式实现多线程间的共享资源的访问,这篇介绍一下线程之间如何进行通信。

之前介绍的内容都是如何保证线程之间的运行互不干扰,但是有的时候,线程之间必须互相合作。比如清洗盘子完成之后,才能对盘子进行烘干操作,烘干必须在清洗之后,那么清洗线程和烘干线程如何进行沟通呢?

Java进程间的通信与访问共享变量一样,都需要借助互斥的特性来实现,在互斥的基础上,JDK为线程提供了一种自我挂起的能力。也就是说想实现进程间的通信,前提必须是在synchronized同步块或者方法中实现。

实现互斥同步机制,Java提供了两种方法。内置锁synchronized和显示锁Lock,所以线程间通信也有两种方式。

第一种方式是基于内置锁,通过Object对象提供的wait()方法以及notify()/notifyAll()方法实现。

第二种方式是基于显示锁,通过Condition对象的await()方法和signal()/signaAll()方法。

内置锁——线程间通信

wait(): 如果当前线程在等待某个条件发生变化之后,才能继续执行,但是触发条件发生变化,超出了当前线程的能力。通常需要另外一个线程来改变这种条件的时候,就需要考虑使用wait()方法,将当前线程挂起,等待条件的改变。

比如想把盘子由清洗状态改为烘干状态,必须由烘干线程完成。只有烘干线程才知道烘干操作什么时候完成。

当调用wait()方法将当前挂起之后,只有在notify()或者notifyAll()方法发生时,这个任务才会被唤醒,继续执行。

wait()方法提供了两种使用形式,第一种接受一个时间参数,表示在此时间范围内执行挂起操作,时间到期后自动恢复。第二种就是不接受时间参数,那么将无限挂起,除非调用了notify()或者notifyAll()方法。

notify()/notifyAll():表示唤醒正在因调用了wait()方法而等待的线程。notify()只唤醒一个等待线程,而notifyAll()会唤醒所有因持有同一把锁而等待的任务。

wait()sleep()的区别 wait()sleep()最大的区别在于,调用wait()方法时,会释放锁资源,而sleep()方法则不会。

显示锁——线程间通信

基于显示锁实现的线程间同步机制是通过Condition对象的await()方法和signal()/signalAll()方法实现的。 await()方法的作用等同于wait()signal()/signalAll()等同于notify()/notifyAll()方法。

示例一

通过示例代码演示一下文章开头描述的场景,一个洗碗线程,一个烘干线程,当碗是湿的时候,则挂起,同时唤醒烘干线程,反之亦然。

定义Dish

public class Dish {
    private boolean isWashing = true;
    //清洗
    public synchronized void washing() throws InterruptedException {
        //如果已经是清洗过的状态,则挂起
        while (isWashing == true){
            wait();
        }
        System.out.println("washing被唤醒,正在washing……");
        Thread.sleep(500);
        isWashing = true;
        //通知其它进程,可以开始工作了。
        notifyAll();
    }
    //烘干
    public synchronized void drying() throws InterruptedException {
        //如果是干的,则挂起
        while (isWashing == false){
            wait();
        }
        System.out.println("drying被唤醒,正在drying....");
        Thread.sleep(500);
        isWashing = false;
        //通知其它线程
        notifyAll();
    }
}

清洗任务

public class WashingTask implements Runnable {
    private Dish dish;
    public WashingTask(Dish dish){
        this.dish = dish;
    }
    @Override
    public void run() {
        //如果是湿,则挂起
        while (true){
            try {
                dish.washing();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

烘干任务

public class DryingTask implements Runnable {
    private Dish dish;
    public DryingTask(Dish dish) {
        this.dish = dish;
    }
    @Override
    public void run() {
        while (true){
            try {
                dish.drying();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试程序

public static void main(String[] args){
    Dish dish = new Dish();

    Thread washer = new Thread(new WashingTask(dish));
    Thread dryer = new Thread(new DryingTask(dish));

    washer.start();
    dryer.start();
}

该程序会交替输出如下结果

drying被唤醒,正在drying....
washing被唤醒,正在washing……
示例二

通过Condition对象的awai()signal()方法实现同样的功能。

通过显示锁实现的Dish

public class DishLock {
    private boolean isWashing = true;
    //显示锁
    private Lock lock = new ReentrantLock();
    //通过显示锁拿到Condition对象
    private Condition condition = lock.newCondition();
    public void washing() throws InterruptedException {
        lock.lock();
        try{
            //如果已经是清洗过的状态,则挂起
            while (isWashing == true){
                condition.await();
            }
            System.out.println("washing被唤醒,正在washing……");
            Thread.sleep(500);
            isWashing = true;
            //通知其它进程,可以开始工作了。
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void drying() throws InterruptedException {
        lock.lock();
        try{
            //如果是干的,则挂起
            while (isWashing == false){
                condition.await();
            }
            System.out.println("drying被唤醒,正在drying....");
            Thread.sleep(500);
            isWashing = false;
            //通知其它线程
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

清洗任务

public class WashingTask implements Runnable {
    private DishLock dishLock;
    public WashingTask(DishLock dishLock) {
        this.dishLock = dishLock;
    }
    @Override
    public void run() {
        //如果是湿,则挂起
        while (true){
            try {
                dishLock.washing();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

烘干任务

public class DryingTask implements Runnable {
    private DishLock dishLock;
    public DryingTask(DishLock dishLock) {
        this.dishLock = dishLock;
    }
    @Override
    public void run() {
        while (true){
            try {
                dishLock.drying();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试程序

public static void main(String[] args){
    DishLock dishLock = new DishLock();
    Thread washer = new Thread(new WashingTask(dishLock));
    Thread dryer = new Thread(new DryingTask(dishLock));
    
    washer.start();
    dryer.start();
}

输出结果同上。

结束

这篇文章讲述了,线程之间如何协作,是典型的生产者消费者模型。下一篇会讲一下Java提供的三个并发编程工具类,闭锁,信号量和栅栏。


推荐阅读

1. Java并发编程那些事儿(一) ——任务与线程

2. Java8的Stream流真香,没体验过的永远不知道

3. Awk这件上古神兵你会用了吗

4. 手把手教你搭建一套ELK日志搜索运维平台

·END·
 

花括号MC

Java·大数据·个人成长

微信号:huakuohao-mc