Java™ 教程(守护阻塞)

栏目: Java · 发布时间: 5年前

内容简介:线程通常必须协调他们的操作,最常见的协调用法是守护阻塞,这样的阻塞首先轮询一个条件,该条件必须为真,然后阻塞才能继续,要正确执行此操作,需要执行许多步骤。例如,假设更有效的守护是调用

守护阻塞

线程通常必须协调他们的操作,最常见的协调用法是守护阻塞,这样的阻塞首先轮询一个条件,该条件必须为真,然后阻塞才能继续,要正确执行此操作,需要执行许多步骤。

例如,假设 guardedJoy 是一个方法,在另一个线程设置了共享变量 joy 之前,该方法不能继续,理论上,这种方法可以简单地循环直到满足条件,但该循环是浪费的,因为它在等待时持续执行。

public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
}

更有效的守护是调用 Object.wait 来挂起当前线程,在另一个线程发出可能发生某些特殊事件的通知之前, wait 的调用不会返回 — 尽管不一定是这个线程正在等待的事件:

public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

始终在测试等待条件的循环内调用 wait ,不要假设中断是针对你正在等待的特定条件,或者条件仍然是 true

像许多暂停执行的方法一样, wait 会抛出 InterruptedException ,在这个例子中,我们可以忽略该异常 — 我们只关心 joy 的值。

为什么这个版本的 guardedJoy 是同步的?假设 d 是我们用来调用 wait 的对象,当一个线程调用 d.wait 时,它必须拥有 d 的固有锁 — 否则抛出一个错误,在同步方法中调用 wait 是获取固有锁的一种简单方法。

当调用 wait 时,线程释放锁并暂停执行,在将来的某个时间,另一个线程将获取相同的锁并调用 Object.notifyAll ,通知等待该锁的所有线程发生了重要的事情:

public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

在第二个线程释放锁之后的一段时间,第一个线程重新获取锁并通过从调用 wait 的返回来恢复。

还有第二种通知方法 notify ,它唤醒单个线程,因为 notify 不允许你指定被唤醒的线程,所以它仅在大规模并行应用程序中有用 — 也就是说,具有大量线程的程序,都做类似的事,在这样的应用程序中,你不关心哪个线程被唤醒。

让我们使用守护阻塞来创建生产者—消费者应用程序,这种应用程序在两个线程之间共享数据:创建数据的生产者和使用数据的消费者。两个线程使用共享对象进行通信,协调至关重要:消费者线程在生产者线程交付之前不得尝试检索数据,如果消费者未检索到旧数据,则生产者线程不得尝试传递新数据。

在此示例中,数据是一系列文本消息,通过 Drop 类型的对象共享:

public class Drop {
    // Message sent from producer
    // to consumer.
    private String message;
    // True if consumer should wait
    // for producer to send message,
    // false if producer should wait for
    // consumer to retrieve message.
    private boolean empty = true;

    public synchronized String take() {
        // Wait until message is
        // available.
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = true;
        // Notify producer that
        // status has changed.
        notifyAll();
        return message;
    }

    public synchronized void put(String message) {
        // Wait until message has
        // been retrieved.
        while (!empty) {
            try { 
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status
        // has changed.
        notifyAll();
    }
}

Producer 中定义的生产者线程发送一系列熟悉的消息,字符串“ DONE ”表示已发送所有消息,为了模拟真实世界应用程序的不可预测性,生产者线程在消息发送之间暂停随机间隔。

import java.util.Random;

public class Producer implements Runnable {
    private Drop drop;

    public Producer(Drop drop) {
        this.drop = drop;
    }

    public void run() {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };
        Random random = new Random();

        for (int i = 0;
             i < importantInfo.length;
             i++) {
            drop.put(importantInfo[i]);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
        drop.put("DONE");
    }
}

Consumer 中定义的消费者线程只是检索消息并将其打印出来,直到它检索到“ DONE ”字符串,该线程也会暂停随机间隔。

import java.util.Random;

public class Consumer implements Runnable {
    private Drop drop;

    public Consumer(Drop drop) {
        this.drop = drop;
    }

    public void run() {
        Random random = new Random();
        for (String message = drop.take();
             ! message.equals("DONE");
             message = drop.take()) {
            System.out.format("MESSAGE RECEIVED: %s%n", message);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
    }
}

最后,这是在 ProducerConsumerExample 中定义的主线程,它启动生产者和消费者线程。

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Drop drop = new Drop();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

Drop 类是为了演示守护阻塞而编写的,为了避免重新造轮子,在尝试编写自己的数据共享对象之前,检查 Java 集合框架中的现有数据结构。

上一篇:并发活性


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

交互设计之路

交互设计之路

库帕 / Chris Ding / 电子工业出版社 / 2006-3 / 38.00元

本书是基于众多商务案例,讲述如何创建更好的、高客户忠诚度的软件产品和基于软件的高科技产品的书。本书列举了很多真实可信的实际例子,说明目前在软件产品和基于软件的高科技产品中,普遍存在着“难用”的问题。作者认为,“难用”问题是由这些产品中存在着的高度“认知摩擦”引起的,而产生这个问题的根源在于现今软件开发过程中欠缺了一个为用户利益着想的前期“交互设计”阶段。“难用”的产品不仅损害了用户的利益,最终也将......一起来看看 《交互设计之路》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试