被守护的代码块

线程通常需要协调它们的动作。最常见的协调方法是被守护的代码块。这样的代码块以在代码块执行之前,对一个总是为真的条件进行轮询。为了能正确完成它,需要遵循一系列的步骤。

假设,guardJoy是一个方法——它不能推进,直到一个共享变量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方法来测试等待条件。不要假设中断就是你所等待的那个。

就像很多会暂停运行的其他方法一样,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中定义,它会启动生产者和消费者的线程。

注意:Drop类是为了说明守护代码块。为了避免重复发明轮子,在发明自己的数据共享类前,先研究下JAVA集合框架中已经有的数据结构。在问题和回答章节会有更多的涉及。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容