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

问题描述 投票:0回答:1

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

有两个线程:一个打印偶数,另一个打印奇数。由于某种原因,他们首先都打印出正确的数字,然后“交换”角色。而且,他们似乎不仅仅打印前 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 的数字。两个线程应该共享一个对象,因此我想出了一个共享对象。因此,由于同一个对象在 2 个线程之间共享,因此同步块应该能够正确更新值。

java multithreading thread-safety wait notify
1个回答
1
投票

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

您声明了两个类,它们基本上执行完全相同的操作:打印数字。唯一不同的是条件:打印的数字必须是奇数还是偶数。您已经可以使用一个

Thread
类来实现这一目标,其中唯一需要参数化的是打印条件。
printOdd()
printEven()
方法几乎是复制/粘贴。

此外,班级职责也没有处理好。您的

SharedObject
类基本上是一个计数器,它不仅跟踪并递增计数值,而且还必须处理两个线程的逻辑,这是不应该落在它上面的。它的唯一目标应该是通过并行执行以一致的方式增加共享值。您应该在您的
Thread
中重定向该打印逻辑,因为如您所见,它们的代码基本上只包含进行一次调用。

回答

您的

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

  1. 其中一个线程获取了 SharedObject 的监视器,比如说 OddThread,这意味着 EvenThread 等待锁释放才能进入同步块。此时,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
    条件。下面的
    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()
    调用开始继续执行
    。当数字是正确的“类型”时,它们都会恢复,增加其值(使其成为相反的类型),然后打印通知语句。这不仅解释了为什么每个线程打印其相反的数字类型,而且还解释了为什么即使在达到最大值 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();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.