在 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
中重定向该打印逻辑,因为如您所见,它们的代码基本上只包含一次调用。
回答
最后,您的 printOdd
和 printEven()
方法中的逻辑存在一些漏洞。两个线程都设法在开始时只打印一次相应的数字类型( System.out.println
没有任何文本)。这是因为:
- 其中一个线程获取SharedObject的监视器,假设是OddThread,这意味着EvenThread等待OddThread释放锁以进入同步块。此时OddThread打印出它对应的数字(1):
if (counter % 2 != 0) {
System.out.println(counter);
}
- 然后,OddThread 将该值递增到 2,打印通知语句并最终通知另一个线程(请记住,当线程执行所有这些操作时,它仍然拥有 SharedObject 的监视器)。
counter++;
System.out.println("OddThread: Notify: Counter: " + counter);
notify();
- 然后,循环结束并测试
while
条件。 fowllowing if
失败,因为数字现在是偶数,OddThread 释放锁并等待直到它收到 EvenThread 的通知。
try {
System.out.println("OddThread: Wait: Counter: " + counter);
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted Exception!");
}
- 然后,EvenThread终于可以进入synchronized语句,打印出它的值,也就是2。
if (counter % 2 == 0) {
System.out.println(counter);
}
- 然后,它将值从 2 递增到 3,打印值为 3 的通知语句(因此它打印两次,第二次打印奇数,即使它是偶数线程),最后通知另一个线程。
counter++;
System.out.println("EvenThread: Notify: Counter: " + counter);
notify();
- 然后,EvenThread 测试
while
条件,失败 if
语句,打印等待语句,然后等待 OddThread 唤醒它。
try {
System.out.println("EvenThread: Wait: Counter: " + counter);
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted Exception!");
}
- 从现在开始,每个线程将从它们最后的
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();
}
}
我确定我的问题有多个答案。但是,我正在学习多线程的基本概念,并且想出了下面的代码。
有两个线程:一个打印偶数,另一个打印奇数。出于某种原因,他们首先打印出正确的号码,然后他们“交换”角色。此外,他们似乎打印的不仅仅是前 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
中重定向该打印逻辑,因为如您所见,它们的代码基本上只包含一次调用。
回答
最后,您的 printOdd
和 printEven()
方法中的逻辑存在一些漏洞。两个线程都设法在开始时只打印一次相应的数字类型( System.out.println
没有任何文本)。这是因为:
- 其中一个线程获取SharedObject的监视器,假设是OddThread,这意味着EvenThread等待OddThread释放锁以进入同步块。此时OddThread打印出它对应的数字(1):
if (counter % 2 != 0) {
System.out.println(counter);
}
- 然后,OddThread 将该值递增到 2,打印通知语句并最终通知另一个线程(请记住,当线程执行所有这些操作时,它仍然拥有 SharedObject 的监视器)。
counter++;
System.out.println("OddThread: Notify: Counter: " + counter);
notify();
- 然后,循环结束并测试
while
条件。 fowllowingif
失败,因为数字现在是偶数,OddThread 释放锁并等待直到它收到 EvenThread 的通知。
try {
System.out.println("OddThread: Wait: Counter: " + counter);
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted Exception!");
}
- 然后,EvenThread终于可以进入synchronized语句,打印出它的值,也就是2。
if (counter % 2 == 0) {
System.out.println(counter);
}
- 然后,它将值从 2 递增到 3,打印值为 3 的通知语句(因此它打印两次,第二次打印奇数,即使它是偶数线程),最后通知另一个线程。
counter++;
System.out.println("EvenThread: Notify: Counter: " + counter);
notify();
- 然后,EvenThread 测试
while
条件,失败if
语句,打印等待语句,然后等待 OddThread 唤醒它。
try {
System.out.println("EvenThread: Wait: Counter: " + counter);
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted Exception!");
}
- 从现在开始,每个线程将从它们最后的
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();
}
}