Java 使用两个线程打印奇偶数
我试图使用wait和notify通过两个线程重复打印偶数和奇数。然而,我已经完成了网站中给出的所有实现。虽然作为第一次多线程开发人员,我试图自己做这件事,但是我没有得到想要的结果。在这里,我将我的代码粘贴到下面:请您回顾并回复我犯错误的地方,并给出更正和解释Java 使用两个线程打印奇偶数,java,multithreading,Java,Multithreading,我试图使用wait和notify通过两个线程重复打印偶数和奇数。然而,我已经完成了网站中给出的所有实现。虽然作为第一次多线程开发人员,我试图自己做这件事,但是我没有得到想要的结果。在这里,我将我的代码粘贴到下面:请您回顾并回复我犯错误的地方,并给出更正和解释 package com.test.printEvenOdd; public class PrintOddEvenNumbers { public static void main(String[] args){
package com.test.printEvenOdd;
public class PrintOddEvenNumbers {
public static void main(String[] args){
String s = new String("");
EvenThread t1= new EvenThread(s);
OddThread t2= new OddThread(s);
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t2);
th1.start();
th2.start();
}
}
class EvenThread implements Runnable{
String s;
EvenThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
if(i%2==0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
s.notify();
}
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class OddThread implements Runnable{
String s;
OddThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(i%2==1){
System.out.println(i);
s.notify();
}
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
您的问题是您的锁定过于保守/限制:
你把锁锁在整个环上;对于两个线程
因此,一个线程进入它的循环;但很快它就无法进步。因为它需要另一个线程才能继续。但是第二个线程甚至不能启动——因为它可以进入它的循环
换言之:为了进步;两个线程都需要能够进入各自的循环;并取得足够的进展,以便另一个线程可以执行其下一步
这就像建造一个只有两个人可以一起离开的房间;但是你只允许一个人进入那个房间
欢迎使用多线程编程;你刚刚创建了你的第一个死锁
并记录:重新布置锁时;确保你得到了正确的信号;这样等待/通知就可以按预期工作
最后:如果你仔细看你的代码;你会发现你复制了很多代码。那总是个坏主意。相反:试着找出哪些部分是真正不同的;还有别的。。。应该在源代码中只存在一次。因此,作为另一个练习:当你重新安排你的代码,让它做它应该做的事情时——如果你能重构它,试一下,这样代码重复的数量就最小化了。我向你保证,这将是一个值得你花时间的锻炼 您的问题是锁定过于保守/限制:
你把锁锁在整个环上;对于两个线程
因此,一个线程进入它的循环;但很快它就无法进步。因为它需要另一个线程才能继续。但是第二个线程甚至不能启动——因为它可以进入它的循环
换言之:为了进步;两个线程都需要能够进入各自的循环;并取得足够的进展,以便另一个线程可以执行其下一步
这就像建造一个只有两个人可以一起离开的房间;但是你只允许一个人进入那个房间
欢迎使用多线程编程;你刚刚创建了你的第一个死锁
并记录:重新布置锁时;确保你得到了正确的信号;这样等待/通知就可以按预期工作
最后:如果你仔细看你的代码;你会发现你复制了很多代码。那总是个坏主意。相反:试着找出哪些部分是真正不同的;还有别的。。。应该在源代码中只存在一次。因此,作为另一个练习:当你重新安排你的代码,让它做它应该做的事情时——如果你能重构它,试一下,这样代码重复的数量就最小化了。我向你保证,这将是一个值得你花时间的锻炼 您应该将等待移动到if块内。Else线程将进入等待,而不通知另一个等待线程,并且这两个线程都将等待
if(i%2==0){
synchronized(s){
System.out.println(i);
try {
s.notify();
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代码有问题。没有必要睡觉。正如前面的响应中所提到的,您同步的速度太快,这是不必要的。不能保证偶数线程是先启动还是奇数线程先启动。这取决于哪个线程首先获得锁。最后,一个线程将永远等待,因为另一个线程可能已经出来了,之后没有人会通知。任何等待代码都应该处理虚假的唤醒,你应该将等待移到if块中。Else线程将进入等待,而不通知另一个等待线程,并且这两个线程都将等待
if(i%2==0){
synchronized(s){
System.out.println(i);
try {
s.notify();
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代码有问题。没有必要睡觉。正如前面的响应中所提到的,您同步的速度太快,这是不必要的。不能保证偶数线程是先启动还是奇数线程先启动。这取决于哪个线程首先获得锁。最后,一个线程将永远等待,因为另一个线程可能已经出来了,之后没有人会通知。任何等待代码都应该处理虚假的唤醒,因为初始代码存在许多问题。关于它们的解释,请参见幽灵猫的答案。一般来说,这种计算对于多线程来说不是很好,因为您显然希望按顺序打印数字。但是,考虑到这种愿望,并且希望使用两个线程交错来实现这一点,您可以按如下方式进行。请注意,此解决方案仍存在一些问题。线 取决于已执行的不同线程是否能够达到其自身的结束条件,这意味着如果只为奇数或偶数创建一个线程,则将进入无限循环
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.IntPredicate;
public class Foo {
public static void main(String[] args) {
// an executor service will handle the thread pool and scheduling
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new NumberPrintAndIncrement(i -> i % 2 != 0));
pool.submit(new NumberPrintAndIncrement(i -> i % 2 == 0));
// you want to shut down the pool when the threads are done
pool.shutdown();
}
}
final class NumberPrintAndIncrement implements Runnable {
// Need a shared lock for accessing and updating the current number
private static final Object LOCK = new Object();
// The number is shared between threads so it needs to be volatile
private static volatile int number = 1;
// Instance variable for letting a particular runnable know if it should
// print the number in it's current state
private final IntPredicate predicate;
NumberPrintAndIncrement(IntPredicate predicate) {
this.predicate = Objects.requireNonNull(predicate);
}
@Override
public void run() {
while (number < 10) {
// this could run at any point and any number of times, but
// that doesn't matter since it is just doing a quick check and
// a possible update. If the number doesn't satisfy the predicate,
// this will just be a no-op. Having a predicate means
// you don't have to rely on wait and notify to try and
// achieve interleaving the number output properly which
// is good due to the liveness problem Rajesh mentioned.
synchronized (LOCK) {
if (predicate.test(number)) {
System.out.println(number);
number++;
}
}
}
}
}
您的初始代码存在许多问题。关于它们的解释,请参见幽灵猫的答案。一般来说,这种计算对于多线程来说不是很好,因为您显然希望按顺序打印数字。但是,考虑到这种愿望,并且希望使用两个线程交错来实现这一点,您可以按如下方式进行。请注意,此解决方案仍存在一些问题。该线程依赖于已执行的另一个线程,以便能够达到其自身的结束条件,这意味着如果只为奇数或偶数创建一个,则将进入无限循环
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.IntPredicate;
public class Foo {
public static void main(String[] args) {
// an executor service will handle the thread pool and scheduling
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new NumberPrintAndIncrement(i -> i % 2 != 0));
pool.submit(new NumberPrintAndIncrement(i -> i % 2 == 0));
// you want to shut down the pool when the threads are done
pool.shutdown();
}
}
final class NumberPrintAndIncrement implements Runnable {
// Need a shared lock for accessing and updating the current number
private static final Object LOCK = new Object();
// The number is shared between threads so it needs to be volatile
private static volatile int number = 1;
// Instance variable for letting a particular runnable know if it should
// print the number in it's current state
private final IntPredicate predicate;
NumberPrintAndIncrement(IntPredicate predicate) {
this.predicate = Objects.requireNonNull(predicate);
}
@Override
public void run() {
while (number < 10) {
// this could run at any point and any number of times, but
// that doesn't matter since it is just doing a quick check and
// a possible update. If the number doesn't satisfy the predicate,
// this will just be a no-op. Having a predicate means
// you don't have to rely on wait and notify to try and
// achieve interleaving the number output properly which
// is good due to the liveness problem Rajesh mentioned.
synchronized (LOCK) {
if (predicate.test(number)) {
System.out.println(number);
number++;
}
}
}
}
}
为了更好地理解正在发生的事情,让我们看一下每个线程中发生的步骤
public class PrintOddEvenNumbers {
public static void main(String[] args){
String s = new String("");
EvenThread t1= new EvenThread(s);
OddThread t2= new OddThread(s);
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t2);
th1.start();
th2.start();
}
}
class EvenThread implements Runnable{
String s;
EvenThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
System.out.println("EvenThread i: " + i);
if(i%2==0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
System.out.println("EvenThread notify");
s.notify();
}
try {
System.out.println("EvenThread waiting..");
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class OddThread implements Runnable{
String s;
OddThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
System.out.println("OddThread i: " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(i%2==1){
System.out.println(i);
System.out.println("OddThread notify");
s.notify();
}
try {
System.out.println("OddThread waiting..");
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
一个简单的解释:
当OddThread达到i/2时,它等待s被释放。
当EvenThread达到i/2时,它还等待释放s。
现在,两个线程都在等待死锁唤醒
发生这种情况是因为需要满足一些条件,以便使用notify唤醒另一个等待的线程,即i%2==1和i%2=0
这不是唯一的问题,但也有一些基本问题
在这种特殊情况下,如果线程在生产环境中,那么线程的使用是不正确的,因为您无论如何都在尝试进行顺序工作,因此为每个任务创建线程的开销会增加不必要的开销。
没有共享的资源,使synchornize变得多余。
您期望一个线程在另一个线程之前获得锁,这不是线程的工作方式——可以是任何一个先获得锁的线程。
为了更好地理解正在发生的事情,让我们看一下每个线程中发生的步骤
public class PrintOddEvenNumbers {
public static void main(String[] args){
String s = new String("");
EvenThread t1= new EvenThread(s);
OddThread t2= new OddThread(s);
Thread th1 = new Thread(t1);
Thread th2 = new Thread(t2);
th1.start();
th2.start();
}
}
class EvenThread implements Runnable{
String s;
EvenThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
System.out.println("EvenThread i: " + i);
if(i%2==0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
System.out.println("EvenThread notify");
s.notify();
}
try {
System.out.println("EvenThread waiting..");
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class OddThread implements Runnable{
String s;
OddThread(String s){
this.s= s;
}
@Override
public void run() {
synchronized(s){
for(int i=1;i<=10;i++){
System.out.println("OddThread i: " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(i%2==1){
System.out.println(i);
System.out.println("OddThread notify");
s.notify();
}
try {
System.out.println("OddThread waiting..");
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
一个简单的解释:
当OddThread达到i/2时,它等待s被释放。
当EvenThread达到i/2时,它还等待释放s。
现在,两个线程都在等待死锁唤醒
发生这种情况是因为需要满足一些条件,以便使用notify唤醒另一个等待的线程,即i%2==1和i%2=0
这不是唯一的问题,但也有一些基本问题
在这种特殊情况下,如果线程在生产环境中,那么线程的使用是不正确的,因为您无论如何都在尝试进行顺序工作,因此为每个任务创建线程的开销会增加不必要的开销。
没有共享的资源,使synchornize变得多余。
您期望一个线程在另一个线程之前获得锁,这不是线程的工作方式——可以是任何一个先获得锁的线程。
你期望什么样的产出?你得到了什么输出?为什么不遵循推荐的模式(例如,在检查条件的while循环中等待等)?您需要添加所需的结果和实际输出。命名提示:s是变量的一个非常纯的名称。为什么不称之为锁定对象;这会直接告诉读者这是怎么回事!使用两个锁对象您希望得到什么输出?你得到了什么输出?为什么不遵循推荐的模式(例如,在检查条件的while循环中等待等)?您需要添加所需的结果和实际输出。命名提示:s是变量的一个非常纯的名称。为什么不称之为锁定对象;这会直接告诉读者这是怎么回事!使用两个锁对象。谢谢你的澄清。我对线程信号有点困惑。因此,在编写代码时无法正确安排它。我正在努力。嗨,我还有一个疑问。在下面的代码中,我使用wait-notify实现了线程间通信,它提供了预期的输出。我的问题是,由于线程调度依赖于jvm,是否有任何保证总是“主线程”将获得第一次执行的机会。如果“子线程”得到第一次机会,通知信号将丢失,“主线程”将永远等待。如何确认“主线程”总是首先执行。下面给出了代码:包com.test.thread;公共类ThreadExample1{public static void mainString[]参数引发中断异常{ThreadChild1 t=new ThreadChild1;t.start;synchronizedt{forint i=1;iYes。感谢您的澄清。我对线程信号有点困惑。因此在编写代码时无法正确安排它。我现在正在处理它。您好,我还有一个疑问。在下面的代码中,我使用wait-notify实现了线程间通信,并提供了预期的输出。我的问题是,是吗由于线程调度依赖于jvm,因此不能保证总是“主线程”有第一次执行的机会。如果“子线程”有第一次执行的机会,通知信号将丢失,“主线程”将丢失
永远是这样。如何确认“主线程”总是首先执行。下面给出了代码:包com.test.thread;公共类ThreadExample1{public static void mainString[]参数引发中断异常{ThreadChild1 t=new ThreadChild1;t.start;synchronizedt{forint i=1;我感谢你的建议。我已经通过了jenkov中给出的链接。这是否意味着每次使用wait时,我都必须在while loop中进行保护以避免虚假唤醒?但是我不明白虚假唤醒的用例可能是什么。谢谢你的建议。我已经通过了jenkov中给出的链接。你知道吗这是否意味着每次我使用wait时,我都必须在while循环中进行保护以避免虚假唤醒?但我不明白虚假唤醒的用例是什么。