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



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

有两个线程:一个打印偶数,另一个打印奇数。由于某种原因,它们都是先打印正确的数字,然后再"交换"。的角色。而且,它们似乎不仅仅打印前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。所以,既然同一个对象在两个线程之间共享,那么同步块应该可以正确地更新值。

在你的实现中有一些关于它的设计,概括和一般逻辑的问题。

你声明了两个类,它们基本上做同样的事情:打印数字。唯一不同的是条件:打印的数字必须是奇数还是偶数。您已经可以通过一个Thread类实现这一点,其中唯一需要参数化的是打印条件。printOdd()printEven()方法几乎是复制/粘贴。

此外,类的职责没有得到很好的处理。您的SharedObject类基本上是一个计数器,它不仅跟踪并增加计数值,而且还必须处理两个线程的逻辑,这是不应该落在它身上的东西。它的唯一目标应该是通过并行执行以一致的方式增加共享值。您应该在Thread中重定向打印逻辑,因为,正如您所看到的,它们的代码基本上由进行单个调用组成。

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

  1. 其中一个线程获取SharedObject的监视器,假设是OddThread,这意味着EvenThread等待OddThread释放锁以进入同步块。此时,OddThread打印其对应的数字(1):
if (counter % 2 != 0) {
System.out.println(counter);
}
  1. 然后,OddThread将值增加到2,打印notify语句并最终通知另一个线程(请记住,当线程正在做所有这些时,它仍然拥有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最终可以进入同步语句并打印其值,即2。
if (counter % 2 == 0) {
System.out.println(counter);
}
  1. 然后,它将值从2增加到3,打印值为3的notify语句(因此它打印两次,第二次打印奇数值,即使它是偶数线程),并最终通知另一个线程。
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();
}
}