从三个不同的线程按顺序打印一二三?

时间:2018-10-13 02:44:32

标签: java multithreading concurrency thread-safety

我正在处理以下面试问题:

  

一个进程中有三个线程。第一个线程打印1 1 1   ...,第二个打印2 2 2 ...,第三个打印3 3 3   ...无休止您如何安排这三个线程以便   打印1 2 3 1 2 3 ...

我想出了下面的代码,该代码使用两个线程打印1 2 1 2 1 2,但是我无法从第三个线程中弄清楚如何打印数字3的条件。

public class PrintOneTwoThree {
  private static boolean isFirst = true;
  private static final Object lock = new Object();

  public static void main(String[] args) {
    // first thread
    new Thread(() -> {
      try {
        synchronized (lock) {
          for (;;) {
            while (!isFirst) {
              lock.wait();
            }
            System.out.print("1 ");
            isFirst = false;
            lock.notify();
          }
        }
      } catch (InterruptedException ignored) {
      }
    }).start();

    // second thread
    new Thread(() -> {
      try {
        synchronized (lock) {
          for (;;) {
            while (isFirst) {
              lock.wait();
            }
            System.out.print("2 ");
            isFirst = true;
            lock.notify();
          }
        }
      } catch (InterruptedException ignored) {
      }
    }).start();
  }
}

如何有效解决此类问题?

3 个答案:

答案 0 :(得分:2)

下面是一个通用的工作示例,该示例使用计数器为n个线程一次授予一个线程的权限:

public class PrintOneTwoThree {
    private static int currentTask;
    private static int totalThreads;
    private static final Object lock = new Object();

    public static void main(String[] args) {
        currentTask = 0;
        totalThreads = 3;

        for (int i = 0; i < totalThreads; i++) {
            createThread(i);
        }
    }

    static void createThread(int id) {
        new Thread(() -> {
            try {
                for (;;) {
                    synchronized (lock) {
                        while (currentTask != id) {
                            lock.wait();
                        }

                        System.out.print(id + 1 + " ");
                        currentTask = (currentTask + 1) % totalThreads;
                        lock.notifyAll();
                    }
                }
            }
            catch (InterruptedException ignored) {}
        }).start();
    }
}

输出:

1 2 3 1 2 3 1 2 3 ...

Try it!

几句话:

  • notify()可以在2线程版本上正常工作(因为该变量上最多有一个其他线程阻塞),但是如果一个线程退出关键部分,则在3+版本中将死锁notify表示条件变量currentTask可用,但错误的线程赢得了获得锁的竞争。我不确定notifyAll()是否在这里是合适的设计,因为只有一个线程可以取得进展,因此似乎可以重新检查条件谓词并使用notify()。 / p>

  • 我将for (;;)移到了同步部分的外面,以使线程安全范围尽可能狭窄。该示例之所以人为设计,是因为如果您想要访问单个资源的确切行为,而没有其他任何事情,那么增加线程的开销就没有意义了,您也可以确定性地在单个线程中完成。在一个真实的示例中,线程在不阻塞条件变量的情况下会在for (;;)循环中的其他位置执行线程安全的工作,因此考虑到这一点似乎是合乎逻辑的。

    < / li>

答案 1 :(得分:1)

代替布尔标志,使用整数计数器,并在除以3时检查余数,并在每次打印后检查增量计数器。 而且由于有多个线程在同一锁上等待,所以最好使用notifyAll

private static int counter = 0;

new Thread(() -> {
        try {
            synchronized (lock) {
                for (;;) {
                    while (counter % 3 != 0) {
                        lock.wait();
                    }
                    System.out.print("1 ");
                    ++counter;
                    lock.notifyAll();
                }
            }
        } catch (InterruptedException ignored) {
        }
    }).start();

//And the same stuff for other two threads just replacing value for remainder in if and value in print

答案 2 :(得分:0)

    public class PrintOneTwoThree {

    public static void main(String[] args) {

            Printers sp = new Printers();

            ExecutorService executor = Executors.newFixedThreadPool(3);
            executor.submit(new FirstNumberProducer(sp, 9));
            executor.submit(new SecondNumberProducer(sp , 9));
            executor.submit(new ThirdNumberProducer(sp , 9));
            executor.shutdown();
        }

    }


    class Printers {

        Semaphore first = new Semaphore(1);
        Semaphore second = new Semaphore(0);
        Semaphore third = new Semaphore(0);

        public void printFirstNumber() {
            try {
                first.acquire();
            }catch(InterruptedException exception) {

            }
            System.out.print("1");
                second.release();
        }

        public void printSecondNumber() {
            try {
                second.acquire();
            }catch(InterruptedException exception) {

            }
            System.out.print("2");
            third.release();
        }

        public void printThirdNumber() {
            try {
                third.acquire();
            }catch(InterruptedException exception) {

            }
            System.out.print("3");
            first.release();
        }

    }

    class FirstNumberProducer implements Runnable {

        Printers sp;
        int index;

        FirstNumberProducer(Printers sp , int index) {
            this.sp = sp;
            this.index = index;
        }

        @Override
        public void run() {
            for(int i = 1 ; i <= index ; i = i + 3 ) {
                sp.printFirstNumber();
            }
        }
    }

    class SecondNumberProducer implements Runnable{
        Printers sp;
        int index;

        SecondNumberProducer(Printers sp , int index) {
            this.sp = sp;
            this.index = index;
        }

        @Override
        public void run() {
            for(int i = 2 ; i <= index ; i = i + 3) {
                sp.printSecondNumber();
            }
        }
    }

    class ThirdNumberProducer implements Runnable{
        Printers sp;
        int index;

        ThirdNumberProducer(Printers sp , int index) {
            this.sp = sp;
            this.index = index;
        }

        @Override
        public void run() {
            for(int i = 3 ; i <= index ; i = i + 3) {
                sp.printThirdNumber();
            }
        }
    }

程序输出为:

 123123123