Java 使用两个线程打印奇偶数

Java 使用两个线程打印奇偶数,java,multithreading,Java,Multithreading,我试图使用wait和notify通过两个线程重复打印偶数和奇数。然而,我已经完成了网站中给出的所有实现。虽然作为第一次多线程开发人员,我试图自己做这件事,但是我没有得到想要的结果。在这里,我将我的代码粘贴到下面:请您回顾并回复我犯错误的地方,并给出更正和解释 package com.test.printEvenOdd; public class PrintOddEvenNumbers { public static void main(String[] args){

我试图使用wait和notify通过两个线程重复打印偶数和奇数。然而,我已经完成了网站中给出的所有实现。虽然作为第一次多线程开发人员,我试图自己做这件事,但是我没有得到想要的结果。在这里,我将我的代码粘贴到下面:请您回顾并回复我犯错误的地方,并给出更正和解释

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循环中进行保护以避免虚假唤醒?但我不明白虚假唤醒的用例是什么。