文档

Java™教程
路径:基本Java类
课程:并发性
首页 > 基本Java类 > 并发性

问题和练习的答案:并发性

问题

  1. 问题:你能将Thread对象传递给Executor.execute吗?这样的调用是否有意义?为什么有或者为什么没有?

    回答:Thread实现了Runnable接口,所以你可以将Thread的实例传递给Executor.execute。然而,以这种方式使用Thread对象并没有意义。如果该对象直接从Thread实例化,它的run方法不会做任何事情。你可以定义一个有用的run方法的Thread子类 - 但是这样的类将实现执行器不会使用的特性。

练习题

  1. 练习: 编译并运行BadThreads.java
    public class BadThreads {
    
        static String message;
    
        private static class CorrectorThread
            extends Thread {
    
            public void run() {
                try {
                    sleep(1000); 
                } catch (InterruptedException e) {}
                // 关键语句 1:
                message = "Mares do eat oats."; 
            }
        }
    
        public static void main(String args[])
            throws InterruptedException {
    
            (new CorrectorThread()).start();
            message = "Mares do not eat oats.";
            Thread.sleep(2000);
            // 关键语句 2:
            System.out.println(message);
        }
    }
    

    该应用程序应该打印出 "Mares do eat oats." 这句话。它能保证总是打印出这个结果吗?如果不能,为什么?改变两个Sleep的参数会有帮助吗?如何确保主线程能看到对message的所有更改?

    解决方案: 该程序几乎总是会打印出 "Mares do eat oats." 这句话。然而,这个结果并不能保证,因为"关键语句 1"和"关键语句 2"之间没有发生在之前的关系。即使"关键语句 1"实际上在"关键语句 2"之前执行,这个发生在之前的关系是关于可见性的,而不是顺序。

    有两种方法可以确保主线程能看到对message的所有更改:

    • 在主线程中保留对CorrectorThread实例的引用。然后在引用message之前,在该实例上调用join
    • 使用同步方法封装message在一个对象中。除了通过这些方法之外,不要直接引用message

    这两种技术都建立了必要的发生在之前的关系,使对message的更改可见。

    第三种技术是将message声明为volatile。这保证了对message的任何写入(如"关键语句 1")与任何后续对message的读取(如"关键语句 2")之间存在发生在之前的关系。但它不能保证"关键语句 1"会确切地在"关键语句 2"之前发生。它们可能按顺序发生,但由于调度的不确定性和sleep的未知粒度,这并不是保证。

    改变两个sleep调用的参数也无济于事,因为这对于确保发生在之前的关系没有任何帮助。

  2. 练习:受保护的块Drop

    解决方案: java.util.concurrent.BlockingQueue 接口定义了一个 get 方法,如果队列为空,则会阻塞,还定义了一个 put 方法,如果队列已满,则会阻塞。这些操作与 Drop 的操作基本相同 - 只是 Drop 不是一个队列!不过,还有另一种看待 Drop 的方式:它是一个容量为零的队列。由于队列中没有空间来存储 任何 元素,每个 get 操作都会阻塞,直到对应的 take 操作,每个 take 操作都会阻塞,直到对应的 get 操作。有一种实现了这种行为的 BlockingQueuejava.util.concurrent.SynchronousQueue

    BlockingQueue 几乎可以无缝替换 Drop。在 Producer 中,主要的问题是使用 BlockingQueue 时,putget 方法会抛出 InterruptedException 异常。这意味着现有的 try 必须向上移动一级:

    import java.util.Random;
    import java.util.concurrent.BlockingQueue;
    
    public class Producer implements Runnable {
        private BlockingQueue<String> drop;
    
        public Producer(BlockingQueue<String> 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();
    
            try {
                for (int i = 0;
                     i < importantInfo.length;
                     i++) {
                    drop.put(importantInfo[i]);
                    Thread.sleep(random.nextInt(5000));
                }
                drop.put("DONE");
            } catch (InterruptedException e) {}
        }
    }
    
    Consumer
    import java.util.Random;
    import java.util.concurrent.BlockingQueue;
    
    public class Consumer implements Runnable {
        private BlockingQueue<String> drop;
    
        public Consumer(BlockingQueue<String> drop) {
            this.drop = drop;
        }
    
        public void run() {
            Random random = new Random();
            try {
                for (String message = drop.take();
                     ! message.equals("DONE");
                     message = drop.take()) {
                    System.out.format("接收到的消息: %s%n",
                                      message);
                    Thread.sleep(random.nextInt(5000));
                }
            } catch (InterruptedException e) {}
        }
    }
    
    生产者消费者示例drop
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.SynchronousQueue;
    
    public class ProducerConsumerExample {
        public static void main(String[] args) {
            BlockingQueue<String> drop =
                new SynchronousQueue<String> ();
            (new Thread(new Producer(drop))).start();
            (new Thread(new Consumer(drop))).start();
        }
    }
    

上一页: 问题和练习:并发性