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

时间:2016-08-19 11:12:14

标签: java multithreading

我试图通过两个线程重复使用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();
                }
            }
        }
    }

}

4 个答案:

答案 0 :(得分:2)

您的问题是锁定 太强保守/限制:

你把锁放在整个循环周围;两个线程。

因此,一个线程进入其循环;但很快它就无法进步。因为它需要其他线程来进步。但是第二个线程甚至无法启动 - 因为它可以完全进入其循环!

换句话说:为了取得进步;两个线程都需要能够进入各自的循环;并取得足够的进展,以便其他线程可以完成下一步。

就像建造一个只有两个人可以一起出去的房间;但是你只允许一个人进入那个房间。

欢迎使用多线程编程;你刚刚创建了第一个死锁

并记录:重新安排锁;确保你获得正确的信号;这样wait / notify可以按预期工作。

最后:如果你仔细看看你的代码;你会发现你复制了很多的代码。这总是一个坏主意。相反:尝试确定哪些部分真的不同;和其他任何东西......应该在源代码中完全存在一次。因此,作为另一个练习:当您重新安排代码以使其完成预期的操作时 - 尝试重构它,以便最大限度地减少代码重复的数量。我保证,这将是值得花时间的练习!

答案 1 :(得分:1)

您应该在“if”块内移动“wait()”。其他线程将在没有通知其他等待线程的情况下等待,并且它们都将等待。

        if(i%2==0){
            synchronized(s){
                System.out.println(i);
                try {
                    s.notify();
                    s.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

代码存在问题。没有必要睡觉。正如之前的回复中所提到的,您正在急切地进行同步,这是不必要的。无法保证是否首先启动线程或首先启动奇数线程。这取决于哪个线程首先获得锁定。最后,一个线程将永远等待,因为另一个线程已经出现,之后没有人会通知。任何wait()代码都应该处理here

的虚假唤醒

答案 2 :(得分:1)

您的初始代码存在许多问题。有关它们的解释,请参阅GhostCat的答案。一般来说,这种计算对于多线程来说并不是很好,因为你(显然)想要按顺序打印数字。但是,考虑到这种需求并希望使用2个线程交错来实现这一点,您可以按如下方式进行。请注意,此解决方案仍存在一些问题。线程依赖于已执行的不同线程,以便能够达到它自己的结束条件,这意味着如果您只为奇数(或偶数)数字创建一个,则进入无限循环。 / p>

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++;
                }
            }
        }
    }
}

答案 3 :(得分:1)

为了更好地理解发生了什么,让我们来看看每个线程中发生的步骤。

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();
                }
            }
        }
    }
}

这将打印:

EvenThread i: 1
EvenThread waiting..
OddThread i: 1
1
OddThread notify
OddThread waiting..
EvenThread i: 2
2
EvenThread notify
EvenThread waiting..
OddThread i: 2
OddThread waiting..

一个简单的解释:

  • 当OddThread达到 2 i时,waits的{​​{1}}将被释放。
  • 当EvenThread达到 2 s时,i的{​​{1}}也会被释放。

现在你有两个线程等待被唤醒(死锁)。

这是因为要使用waits snotify唤醒其他等待线程需要满足的条件。

这不是唯一的问题,但也存在一些基本问题。

  • 在这种特殊情况下,如果线程在生产中使用是不正确的,因为你正在尝试进行顺序工作,因此为每个任务创建线程的开销会增加不必要的开销。
  • 没有共享资源,导致i%2==1多余。
  • 你希望一个Thread在另一个之前获得一个锁定,这不是Thread的工作方式 - 它可以是一个先获得锁定的。