Logo
Section 1 / 5

有序打印

这个问题涉及对线程执行顺序施加控制。

导入

有序打印

这个问题涉及对线程执行顺序施加控制。

问题描述

假设有三个线程 t1、t2 和 t3。t1 打印 First,t2 打印 Second,t3 打印 Third。该类的代码如下:

1public class OrderedPrinting { 2 3 public void printFirst() { 4 System.out.print("First"); 5 } 6 7 public void printSecond() { 8 System.out.print("Second"); 9 } 10 11 public void printThird() { 12 System.out.print("Third"); 13 } 14 15}

线程 t1 调用 printFirst(),t2 调用 printSecond(),t3 调用 printThird()。这些线程可以以任意顺序运行。你需要对这些线程进行同步,使得函数 printFirst()、printSecond() 和 printThird() 按照顺序执行。

程序的工作流程如下图所示:

...

解决方案

我们为这个问题提供两种解决方案:一种使用基本的 wait() 和 notifyAll() 方法,另一种使用 CountDownLatch。

方案一

在此方案中,我们定义一个类 OrderedPrinting,其中包含一个私有变量 count。该类包含三个方法:printFirst()、printSecond() 和 printThird()。类的基本结构如下:

1class OrderedPrinting { 2 int count; 3 4 public OrderedPrinting() { 5 count = 1; 6 } 7 8 public void printFirst() { 9 } 10 11 public void printSecond() { 12 } 13 14 public void printThird() { 15 } 16}

在构造函数中,count 被初始化为 1。接下来我们详细说明 printFirst() 方法:

1public void printFirst() throws InterruptedException { 2 3 synchronized(this) { 4 System.out.println("First"); 5 count++; // 为打印 Second,递增 count 6 this.notifyAll(); 7 } 8}

在 printFirst() 中,首先打印 "First"。此处无需检查 count 的值。打印完成后,将 count 加 1,为下一个要打印的单词做准备。然后调用 notifyAll() 唤醒所有等待中的线程,通知它们继续执行。

1public void printSecond() throws InterruptedException { 2 3 synchronized(this) { 4 while(count != 2) { 5 this.wait(); 6 } 7 System.out.println("Second"); 8 count++; 9 this.notifyAll(); 10 } 11}

在第二个方法中,会检查 count 的值。如果其不等于 2,则调用线程进入等待状态。当 count 变为 2 时,while 循环结束,打印 "Second"。随后将 count 加 1,并调用 notifyAll() 唤醒其他可能等待的线程。

1public void printThird() throws InterruptedException { 2 3 synchronized(this) { 4 while(count != 3) { 5 this.wait(); 6 } 7 System.out.println("Third"); 8 } 9}

第三个方法的逻辑与第二个类似,唯一的区别是判断 count 是否等于 3。若相等,则打印 "Third";否则调用线程保持等待。

为了运行上述解决方案,我们将创建另一个类来实现多线程。通过继承 Thread 类,每个线程都会创建一个独立的对象并与父类关联。该类包含两个变量:一个是 OrderedPrinting 类型的对象,另一个是字符串类型的 method 变量,用于指定应调用 OrderedPrinting 中的哪个方法。

1class OrderedPrintingThread extends Thread { 2 private OrderedPrinting obj; 3 private String method; 4 5 public OrderedPrintingThread(OrderedPrinting obj, String method) { 6 this.method = method; 7 this.obj = obj; 8 } 9 10 public void run() { 11 // 打印 "First" 12 if ("first".equals(method)) { 13 try { 14 obj.printFirst(); 15 } 16 catch(InterruptedException e) { 17 18 } 19 } 20 // 打印 "Second" 21 else if ("second".equals(method)) { 22 try { 23 obj.printSecond(); 24 } 25 catch(InterruptedException e) { 26 27 } 28 } 29 // 打印 "Third" 30 else if ("third".equals(method)) { 31 try { 32 obj.printThird(); 33 } 34 catch(InterruptedException e) { 35 36 } 37 } 38 } 39}

我们将在 Main 类中创建三个线程以测试每种解决方案。这三个线程将共享同一个 OrderedPrinting 实例。线程 t1 调用 printFirst(),t2 调用 printSecond(),t3 调用 printThird()。最终输出结果始终按照 first、second、third 的正确顺序打印,不受线程启动顺序的影响。

1class OrderedPrinting { 2 3 int count; 4 5 public OrderedPrinting() { 6 count = 1; 7 } 8 9 public void printFirst() throws InterruptedException { 10 11 synchronized(this){ 12 System.out.println("First"); 13 count++; 14 this.notifyAll(); 15 } 16 } 17 18 public void printSecond() throws InterruptedException { 19 20 synchronized(this){ 21 while(count != 2){ 22 this.wait(); 23 } 24 System.out.println("Second"); 25 count++; 26 this.notifyAll(); 27 } 28 29 } 30 31 public void printThird() throws InterruptedException { 32 33 synchronized(this){ 34 while(count != 3){ 35 this.wait(); 36 } 37 System.out.println("Third"); 38 } 39 40 } 41} 42 43class OrderedPrintingThread extends Thread 44{ 45 private OrderedPrinting obj; 46 private String method; 47 48 public OrderedPrintingThread(OrderedPrinting obj, String method) 49 { 50 this.method = method; 51 this.obj = obj; 52 } 53 54 public void run() 55 { 56 // 打印 "First" 57 if ("first".equals(method)) 58 { 59 try 60 { 61 obj.printFirst(); 62 } 63 catch(InterruptedException e) 64 { 65 66 } 67 } 68 // 打印 "Second" 69 else if ("second".equals(method)) 70 { 71 try 72 { 73 obj.printSecond(); 74 } 75 catch(InterruptedException e) 76 { 77 78 } 79 } 80 // 打印 "Third" 81 else if ("third".equals(method)) 82 { 83 try 84 { 85 obj.printThird(); 86 } 87 catch(InterruptedException e) 88 { 89 90 } 91 } 92 } 93} 94 95public class Main 96{ 97 public static void main(String[] args) 98 { 99 OrderedPrinting obj = new OrderedPrinting(); 100 101 OrderedPrintingThread t1 = new OrderedPrintingThread(obj, "first"); 102 OrderedPrintingThread t2 = new OrderedPrintingThread(obj, "second"); 103 OrderedPrintingThread t3 = new OrderedPrintingThread(obj, "third"); 104 105 t2.start(); 106 t3.start(); 107 t1.start(); 108 109 } 110}

使用 CountDownLatch 的解决方案

第二种方案使用 CountDownLatch —— 一种用于实现并发控制的同步工具。它适用于需要保证操作或任务按特定顺序执行的多线程场景。每当一个线程完成任务后,就会调用 countDown() 方法,使计数器减 1。当计数器归零时,之前调用 await() 的线程会被唤醒,从而恢复执行。

OrderedPrinting 类的基本结构与方案一相同,唯一的区别是使用 CountDownLatch 替代了方案一中的 count 和 wait()/notifyAll() 机制。我们定义了两个 CountDownLatch 变量,初始值均为 1。

1class OrderedPrinting { 2 CountDownLatch latch1; 3 CountDownLatch latch2; 4 5 public OrderedPrinting() { 6 latch1 = new CountDownLatch(1); 7 latch2 = new CountDownLatch(1); 8 } 9}

在 printFirst() 方法中,打印完成后调用 latch1.countDown(),使其计数减至 0,从而唤醒等待的线程。printSecond() 方法会先调用 latch1.await() 等待第一个操作完成,之后打印 "Second" 并调用 latch2.countDown()。类似地,在 printThird() 方法中,会等待 latch2 归零后再进行打印。这里的门闩(latch)就像开关或闸门,控制着各个操作的通行时机。

1public void printFirst() throws InterruptedException { 2 // 打印并通知等待的线程 3 System.out.println("First"); 4 latch1.countDown(); 5}
1public void printSecond() throws InterruptedException { 2 // 如果 "First" 尚未打印,则等待 3 latch1.await(); 4 // 打印并通知等待的线程 5 System.out.println("Second"); 6 latch2.countDown(); 7}
1public void printThird() throws InterruptedException { 2 // 如果 "Second" 尚未打印,则等待 3 latch2.await(); 4 System.out.println("Third"); 5}

与前一种方案类似,我们创建了继承自 Thread 类的 OrderedPrintingThread 类。此类的细节已在上文详述。

1import java.util.concurrent.CountDownLatch; 2 3class OrderedPrinting 4{ 5 CountDownLatch latch1; 6 CountDownLatch latch2; 7 8 public OrderedPrinting() 9 { 10 latch1 = new CountDownLatch(1); 11 latch2 = new CountDownLatch(1); 12 } 13 14 public void printFirst() throws InterruptedException 15 { 16 System.out.println("First"); 17 latch1.countDown(); 18 } 19 20 public void printSecond() throws InterruptedException 21 { 22 latch1.await(); 23 System.out.println("Second"); 24 latch2.countDown(); 25 } 26 27 public void printThird() throws InterruptedException 28 { 29 latch2.await(); 30 System.out.println("Third"); 31 } 32} 33 34class OrderedPrintingThread extends Thread 35{ 36 private OrderedPrinting obj; 37 private String method; 38 39 public OrderedPrintingThread(OrderedPrinting obj, String method) 40 { 41 this.method = method; 42 this.obj = obj; 43 } 44 45 public void run() 46 { 47 if ("first".equals(method)) 48 { 49 try 50 { 51 obj.printFirst(); 52 } 53 catch(InterruptedException e) 54 { 55 56 } 57 } 58 else if ("second".equals(method)) 59 { 60 try 61 { 62 obj.printSecond(); 63 } 64 catch(InterruptedException e) 65 { 66 67 } 68 } 69 else if ("third".equals(method)) 70 { 71 try 72 { 73 obj.printThird(); 74 } 75 catch(InterruptedException e) 76 { 77 78 } 79 } 80 } 81} 82 83public class Main 84{ 85 public static void main(String[] args) 86 { 87 OrderedPrinting obj = new OrderedPrinting(); 88 89 OrderedPrintingThread t1 = new OrderedPrintingThread(obj, "first"); 90 OrderedPrintingThread t2 = new OrderedPrintingThread(obj, "second"); 91 OrderedPrintingThread t3 = new OrderedPrintingThread(obj, "third"); 92 93 t3.start(); 94 t2.start(); 95 t1.start(); 96 97 } 98}

面试练习题

打印 Foo Bar n 次