【问题标题】:Multi threaded java program to print even and odd numbers alternatively多线程java程序交替打印偶数和奇数
【发布时间】:2015-11-06 05:51:57
【问题描述】:

在一次采访中,我被要求编写一个双线程 Java 程序。在这个程序中,一个线程应该打印偶数,另一个线程应该交替打印奇数。

样本输出:

线程1:1

线程2:2

线程1:3

线程2:4 ...等等

我编写了以下程序。一类Task,其中包含两种分别打印偶数和奇数的方法。在 main 方法中,我创建了两个线程来调用这两个方法。面试官要求我进一步改进,但我想不出任何改进。有没有更好的方法来编写相同的程序?

class Task
{
    boolean flag;

    public Task(boolean flag)
    {
        this.flag = flag;
    }
    public void printEven()
    {
        for( int i = 2; i <= 10; i+=2 )
        {
            synchronized (this)
            {
                try
                {
                    while( !flag )
                        wait();
                    System.out.println(i);
                    flag = false;
                    notify();
                }
                catch (InterruptedException ex)
                {
                    ex.printStackTrace();
                }
            }
        }
    }
    public void printOdd()
    {
        for( int i = 1; i < 10; i+=2 )
        {
            synchronized (this)
            {
                try
                {
                    while(flag )
                        wait();
                    System.out.println(i);
                    flag = true;
                    notify();
                }
                catch(InterruptedException ex)
                {
                    ex.printStackTrace();
                }
            }
        }
    }
}

public class App {
    public static void main(String [] args)
    {
        Task t = new Task(false);
        Thread t1 = new Thread( new Runnable() {
           public void run()
           {
               t.printOdd();
           }
        });
        Thread t2 = new Thread( new Runnable() {
            public void run()
            {
                t.printEven();
            }
        });
        t1.start();
        t2.start();
    }
}

【问题讨论】:

  • 不确定。但也许面试官正在寻找使用CyclicBarrier
  • 一种可能的改进是以无锁方式进行。

标签: java multithreading synchronization


【解决方案1】:

我认为这应该可以正常工作并且非常简单。

package com.simple;

import java.util.concurrent.Semaphore;

/**
 * @author Evgeny Zhuravlev
 */
public class ConcurrentPing
{
    public static void main(String[] args) throws InterruptedException
    {
        Semaphore semaphore1 = new Semaphore(0, true);
        Semaphore semaphore2 = new Semaphore(0, true);
        new Thread(new Task("1", 1, semaphore1, semaphore2)).start();
        new Thread(new Task("2", 2, semaphore2, semaphore1)).start();
        semaphore1.release();
    }

    private static class Task implements Runnable
    {
        private String name;
        private long value;
        private Semaphore semaphore1;
        private Semaphore semaphore2;

        public Task(String name, long value, Semaphore semaphore1, Semaphore semaphore2)
        {
            this.name = name;
            this.value = value;
            this.semaphore1 = semaphore1;
            this.semaphore2 = semaphore2;
        }

        @Override
        public void run()
        {
            while (true)
            {
                try
                {
                    semaphore1.acquire();
                    System.out.println(name + ": " + value);
                    value += 2;
                    semaphore2.release();
                }
                catch (InterruptedException e)
                {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

【讨论】:

    【解决方案2】:

    嗯,有很多选择。我可能会改用SynchronousQueue(我不喜欢低级别的wait/notify,而是尝试使用更高级别的并发原语)。 printOddprintEven 也可以合并为一个方法,不需要额外的标志:

    public class App {
        static class OddEven implements Runnable {
            private final SynchronousQueue<Integer> queue = new SynchronousQueue<>();
    
            public void start() throws InterruptedException {
                Thread oddThread = new Thread(this);
                Thread evenThread = new Thread(this);
                oddThread.start();
                queue.put(1);
                evenThread.start();
            }
    
            @Override
            public void run() {
                try {
                    while (true) {
                        int i = queue.take();
                        System.out.println(i + " (" + Thread.currentThread() + ")");
                        if (i == 10)
                            break;
                        queue.put(++i);
                        if (i == 10)
                            break;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new OddEven().start();
        }
    }
    

    【讨论】:

      【解决方案3】:

      这样的短版本怎么样:

      public class OddEven implements Runnable {
          private static volatile int n = 1;
      
          public static void main(String [] args) {
              new Thread(new OddEven()).start();
              new Thread(new OddEven()).start();
          }
      
          @Override
          public void run() {
              synchronized (this.getClass()) {
                  try {
                      while (n < 10) {
                          this.getClass().notify();
                          this.getClass().wait();
                          System.out.println(Thread.currentThread().getName() + ": " + (n++));
                          this.getClass().notify();
                      }
                  } catch (InterruptedException ex) {
                      ex.printStackTrace();
                  }
              }
          }   
      }
      

      正确启动线程有一点技巧——因此需要额外的notify() 来启动整个事情(而不是让两个进程都等待,或者需要主线程调用通知)并且还要处理线程启动的可能性,它是否正常工作并在第二个线程启动之前调用通知:)

      【讨论】:

      • 虚假唤醒怎么样?
      • 通知丢失怎么办?
      【解决方案4】:

      有没有更好的方法来编写相同的程序?

      嗯,问题是,编写程序的唯一方法是使用单线程。如果你想让一个程序按 X、Y 和 Z 的顺序执行,那么编写一个执行 X、Y、Z 的过程。没有比这更好的方法了。

      这是我在与面试官讨论线程的适当性后会写的内容。

      import java.util.concurrent.SynchronousQueue;
      import java.util.function.Consumer;
      
      public class EvenOdd {
          public static void main(String[] args) {
              SynchronousQueue<Object> q1 = new SynchronousQueue<>();
              SynchronousQueue<Object> q2 = new SynchronousQueue<>();
              Consumer<Integer> consumer = (Integer count) -> System.out.println(count);
              new Thread(new Counter(q1, q2, 2, 1, consumer)).start();
              new Thread(new Counter(q2, q1, 2, 2, consumer)).start();
              try {
                  q1.put(new Object());
              } catch (InterruptedException ex) {
                  throw new RuntimeException(ex);
              }
          }
      
          private static class Counter implements Runnable {
              final SynchronousQueue<Object> qin;
              final SynchronousQueue<Object> qout;
              final int increment;
              final Consumer<Integer> consumer;
              int count;
      
              Counter(SynchronousQueue<Object> qin, SynchronousQueue<Object> qout,
                      int increment, int initial_count,
                      Consumer<Integer> consumer) {
                  this.qin = qin;
                  this.qout = qout;
                  this.increment = increment;
                  this.count = initial_count;
                  this.consumer = consumer;
              }
      
              public void run() {
                  try {
                      while (true) {
                          Object token = qin.take();
                          consumer.accept(count);
                          qout.put(token);
                          count += increment;
                      }
                  } catch (InterruptedException ex) {
                      throw new RuntimeException(ex);
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        我最初的回答是无效的。编辑:

        package test;
        
        public final class App {
        
            private static volatile int counter = 1;
            private static final Object lock = new Object();
        
            public static void main(String... args) {
                for (int t = 0; t < 2; ++t) {
                    final int oddOrEven = t;
                    new Thread(new Runnable() {
                        @Override public void run() {
                            while (counter < 100) {
                                synchronized (lock) {
                                    if (counter % 2 == oddOrEven) {
                                        System.out.println(counter++);
                                    }
                                }
                            }
                        }
                    }).start();
                }
            }
        }
        

        【讨论】:

        • 一、编译错误(t不是final)。其次,这并不意味着任何 打印 顺序。您在条件检查和打印之间存在竞争。最后,增加 volatile 变量是错误的。增量不是原子的。
        • 这段代码不能完全按原样工作,我必须将 Runnable 提取到一个单独的具体类中,该类实现 Runnable,构造函数接受t。它也不完全工作:1、3、2、4、5、6、8、7、9、11、10,System.out.println(counter++); 行表示集成计数器,然后以非原子方式打印它,这意味着println 语句有可能在线程之间以错误的顺序结束。
        • 如果你改为System.out.println(counter); counter++;,它会起作用
        • 没错,有很多问题,现在上面有修订版。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-14
        • 2011-08-26
        • 2013-05-17
        • 1970-01-01
        • 1970-01-01
        • 2022-11-26
        相关资源
        最近更新 更多