在 Java 中使用 2 个线程打印奇偶数

Printing OddEven Number using 2 Threads in Java

我确定我的问题有多个答案。但是,我正在学习多线程的基本概念,并且想出了下面的代码。

有两个线程:一个打印偶数,另一个打印奇数。出于某种原因,他们首先打印出正确的号码,然后他们“交换”角色。此外,他们似乎打印的不仅仅是前 10 个数字。

为什么输出不正确?

package com.thread;

public class OddEventThread {

    public static void main(String[] args) {
        SharedResource obj = new SharedResource();

        OddThread oddThread = new OddThread(obj);
        EvenThread evenThread = new EvenThread(obj);

        System.out.println("Starting Odd/Even Thread");

        oddThread.start();
        evenThread.start();
    }

}

class OddThread extends Thread {

    SharedResource obj;

    public OddThread(SharedResource obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        System.out.println("OddThread");
        obj.printOdd();
    }
}

class EvenThread extends Thread {

    SharedResource obj;

    public EvenThread(SharedResource obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        System.out.println("EvenThread");
        obj.printEven();
    }
}

class SharedResource {

    private int N = 10;

    private int counter = 1;

    public void printOdd() {
        System.out.println("printOdd");
        synchronized (this) {
            System.out.println("OddThread: Counter: " + counter);
            while (counter <= N) {
                if (counter % 2 != 0) {
                    System.out.println(counter);
                } else {
                    try {
                        System.out.println("OddThread: Wait: Counter: " + counter);
                        wait();
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted Exception!");
                    }
                }
                counter++;
                System.out.println("OddThread: Notify: Counter: " + counter);
                notify();
            }
        }
    }

    public void printEven() {
        System.out.println("printEven");
        synchronized (this) {
            System.out.println("EvenThread: Counter: " + counter);
            while (counter <= N) {
                if (counter % 2 == 0) {
                    System.out.println(counter);
                } else {
                    try {
                        System.out.println("EvenThread: Wait: Counter: " + counter);
                        wait();
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted Exception!");
                    }
                }
                counter++;
                System.out.println("EvenThread: Notify: Counter: " + counter);
                notify();
            }
        }
    }
}

输出:

Starting Odd/Even Thread
OddThread
printOdd
EvenThread
printEven
OddThread: Counter: 1
1
OddThread: Notify: Counter: 2
OddThread: Wait: Counter: 2
EvenThread: Counter: 2
2
EvenThread: Notify: Counter: 3
EvenThread: Wait: Counter: 3
OddThread: Notify: Counter: 4
OddThread: Wait: Counter: 4
EvenThread: Notify: Counter: 5
EvenThread: Wait: Counter: 5
OddThread: Notify: Counter: 6
OddThread: Wait: Counter: 6
EvenThread: Notify: Counter: 7
EvenThread: Wait: Counter: 7
OddThread: Notify: Counter: 8
OddThread: Wait: Counter: 8
EvenThread: Notify: Counter: 9
EvenThread: Wait: Counter: 9
OddThread: Notify: Counter: 10
OddThread: Wait: Counter: 10
EvenThread: Notify: Counter: 11
OddThread: Notify: Counter: 12

这是我提出这个解决方案的思考过程:

我们有 2 个线程打印从 1 到 10 的数字。两个线程应该共享一个对象,因此我想出了一个 sharedObj。因此,由于同一个对象在 2 个线程之间共享,因此同步块应该可以正确更新值。

您的实现在设计、泛化和通用逻辑方面存在一些问题。

您正在声明两个 classes,它们基本上做同样的事情:打印数字。唯一不同的是条件:打印的数字必须是奇数还是偶数。您已经可以使用一个 Thread class 来实现此目的,其中唯一要参数化的是打印条件。 printOdd()printEven() 方法几乎是 copy/paste.

此外,class的职责也没有得到很好的处理。你的 SharedObject class 基本上是一个计数器,它不仅跟踪并增加计数值,而且还必须处理两个线程的逻辑,这是不应该落在它身上的。它的唯一目标应该是以与并行执行一致的方式增加共享值。您应该在 Thread 中重定向该打印逻辑,因为如您所见,它们的代码基本上只包含一次调用。

回答

最后,您的 printOddprintEven() 方法中的逻辑存在一些漏洞。两个线程都设法在开始时只打印一次相应的数字类型( System.out.println 没有任何文本)。这是因为:

  1. 其中一个线程获取SharedObject的监视器,假设是OddThread,这意味着EvenThread等待OddThread释放锁以进入同步块。此时OddThread打印出它对应的数字(1):
if (counter % 2 != 0) {
    System.out.println(counter);
}
  1. 然后,OddThread 将该值递增到 2,打印通知语句并最终通知另一个线程(请记住,当线程执行所有这些操作时,它仍然拥有 SharedObject 的监视器)。
counter++;
System.out.println("OddThread: Notify: Counter: " + counter);
notify();
  1. 然后,循环结束并测试while条件。 fowllowing if 失败,因为数字现在是偶数,OddThread 释放锁并等待直到它收到 EvenThread 的通知。
try {
    System.out.println("OddThread: Wait: Counter: " + counter);
    wait();
} catch (InterruptedException e) {
    System.out.println("Interrupted Exception!");
}
  1. 然后,EvenThread终于可以进入synchronized语句,打印出它的值,也就是2。
if (counter % 2 == 0) {
    System.out.println(counter);
}
  1. 然后,它将值从 2 递增到 3,打印值为 3 的通知语句(因此它打印两次,第二次打印奇数,即使它是偶数线程),最后通知另一个线程。
counter++;
System.out.println("EvenThread: Notify: Counter: " + counter);
notify();
  1. 然后,EvenThread 测试 while 条件,失败 if 语句,打印等待语句,然后等待 OddThread 唤醒它。
try {
    System.out.println("EvenThread: Wait: Counter: " + counter);
    wait();
} catch (InterruptedException e) {
    System.out.println("Interrupted Exception!");
}
  1. 从现在开始,每个线程将从它们最后的 wait() 调用 继续。当数字是他们正确的“类型”时,他们都恢复,但随后他们增加它的值,使其成为相反的类型,然后打印 notify 语句。这不仅解释了为什么每个线程打印它们相反的数字类型,而且还解释了为什么即使在达到最大值 10 后它们仍继续打印。它们都在到达最后一次 while 迭代后又增加了一次,因为它们都从最后一次 wait() 调用中恢复。

解决方案

这是一个修复了所有设计、泛化和逻辑漏洞的实现。

class Main {

    public static void main(String[] args) {
        SharedCounter counter = new SharedCounter();

        ThreadPrintingNums oddThread = new ThreadPrintingNums("OddThread", counter, false, 10);
        ThreadPrintingNums evenThread = new ThreadPrintingNums("EvenThread",counter, true, 10);

        System.out.println("Starting Threads");

        oddThread.start();
        evenThread.start();
    }
}

class ThreadPrintingNums extends Thread {

    private SharedCounter counter;
    private boolean flagPrintEven;
    private int max;

    public ThreadPrintingNums(String threadName, SharedCounter obj, boolean flagPrintEven, int max) {
        setName(threadName);
        this.counter = obj;
        this.flagPrintEven = flagPrintEven;
        this.max = max;
    }

    @Override
    public void run() {
        while (counter.getCounter() <= max) {
            if (counter.getCounter() % 2 == (flagPrintEven ? 0 : 1)) {
                System.out.printf("%s => %d%n", getName(), counter.getCounter());
                counter.incCounter();
            } else {
                try {
                    synchronized (counter) {
                        counter.wait();
                    }
                } catch (InterruptedException e) {
                    System.out.printf("%s interrupted exception", getName());
                    System.exit(-1);
                }
            }
        }
    }
}

class SharedCounter {

    private int counter;

    public SharedCounter() {
        this.counter = 1;
    }

    public synchronized int getCounter() {
        return counter;
    }

    public synchronized void incCounter() {
        counter++;
        notify();
    }
}