按特定顺序仅从5个线程打印1到10的整数

时间:2018-09-27 11:10:34

标签: java multithreading

我最近开始使用Java多线程

我在解决一个问题时遇到问题,我只有5个线程,范围从 T1,T2,... T5

任务是按以下顺序打印从1到10的数字。

T1 -> 1
T2 -> 2
T3 -> 3
T4 -> 4
T5 -> 5
T1 -> 6
T2 -> 7
T3 -> 8
T4 -> 9
T5 -> 10

我试图用这段代码解决它。

public static void main(String[] args) throws InterruptedException {
    Counter counter = new Counter();
    Thread[] tArray = new Thread[] { new Thread(counter, "T1"), new Thread(counter, "T2"),
            new Thread(counter, "T3"), new Thread(counter, "T4"), new Thread(counter, "T5") };
    for (int i = 0; i < 10; i++) {
        if (i < 5) {
            tArray[i].start();
            tArray[i].join();
        } else {
            tArray[i - 5] = new Thread(counter, "T" + ((i - 5) + 1)); //Instantiating new Thread which is not allowed.
            tArray[i - 5].start();
            tArray[i - 5].join();
        }
    }
}

public class Counter implements Runnable {

    private int count = 0;

    @Override
    public synchronized void run() {
       System.out.println(Thread.currentThread().getName() + " -> " + ++count);
    }

}

但是由于只允许5个线程,所以我的解决方案不被接受,因为我还在new Thread循环的else块中实例化了for

任何帮助将不胜感激。

5 个答案:

答案 0 :(得分:5)

您需要安排线程之间的交互。线程交互的最自然的方法是建立连接线程的阻塞队列。队列可以是独立的对象,也可以属于特定线程。 在您的情况下,您需要绕5个线程。

class CountPrinter extends Thread {
   String name;
   ArrayBlockingQueue<Integer> inp = new ArrayBlockingQueue<>();
   CountPrinter next;

   public void run() {
      for (;;)
         int n = inp.take();
         if (n == 11) {// only 10 numbers must be printed
            next.inp.put(11);
            return;
         }
         System.out.println(name+"->"+n);
         next.inp.put(n+1);
      }
   }
}

在创建线程之后和开始之前,您需要分配字段namenext。我相信您可以自己对此进行编程。 另外,必须为第一个线程提供初始值1

答案 1 :(得分:1)

免责声明:我正在回答OP问题的实际对应部分-带有串行输入和输出的并行处理。有趣得多。

思考过程

  1. 我有一个串行资源-System.out。无论我如何构造代码,都将在其前面出现显式或隐式队列/争用。
  2. 处理争用的最佳方法是通过显式排队(可以观察,量化和寻址,与使用互斥锁或同步块上的隐式队列相反)。
  3. 我是3个步骤的管道:ProduceStringizeOutput
  4. Stringize步骤可以并行完成,只要有序的Output仍然可以发生。
  5. 我从一个快速且肮脏的“穷人”解决方案开始。对于Java 8,这将与CompletableFuture -s:

    final Executor inputWorker = newSingleThreadExecutor();
    final Executor processingPool = newFixedThreadPool(3);
    final Executor outputWorker = newSingleThreadExecutor();
    
    final int[] counter = {-1}; // this emulates a non-thread-safe information source
    CompletableFuture<Void> future = completedFuture(null);
    for (int i = 0; i < 10; ++i) {
        future = future // chaining of futures is essential for serializing subsequent iterations
                .thenApplyAsync(unused -> ++counter[0], inputWorker)
                .thenApplyAsync(Objects::toString, processingPool)
                .thenAcceptAsync(System.out::println, outputWorker);
    }
    future.join();
    
  6. 一旦我有一个很好的直觉,它就会如何工作,我可能会考虑使用诸如actorsdisruptor之类的工业技术,或者类似的技术来进一步改善它。

P.S。 -为完整起见,可能希望第5步略有不同,首先创建整个计算时间表,然后触发它:

final Executor producer = newSingleThreadExecutor();
final Executor stringizer = newFixedThreadPool(3);
final Executor printer = newSingleThreadExecutor();

final int[] counter = {-1}; // this emulates a non-thread-safe information source

System.out.println("creating schedule...");
// first schedule the whole amount of work and block the execution on a single "trigger" future
final CompletableFuture<Void> trigger = new CompletableFuture<>();
CompletableFuture<Void> future = trigger;
for (int i = 0; i < 10; ++i) {
    future = future
            .thenApplyAsync(unused -> ++counter[0], producer)
            .thenApplyAsync(Objects::toString, stringizer)
            .thenAcceptAsync(System.out::println, printer);
}

// then pull the trigger
System.out.println("pulling the trigger...");
trigger.complete(null);
future.join();

答案 2 :(得分:0)

另一种方法是保留两个OnStart,如下所示:

AtomicInteger

用法为:

static class MyRunnable implements Runnable {

    private final AtomicInteger index;
    private final AtomicInteger ai;
    private final int[] array;
    private final int current;
    private final int next;

    public MyRunnable(AtomicInteger index, AtomicInteger ai, int[] array, int current, int next) {
        this.index = index;
        this.ai = ai;
        this.array = array;
        this.current = current;
        this.next = next;
    }

    @Override
    public void run() {
        for (;;) {
            if (index.get() == array.length) {
                break;
            }
            if (ai.get() == current) {
                System.out.println(Thread.currentThread().getName() + " " + array[index.getAndIncrement()]);
                ai.compareAndSet(current, next);
            }
        }
    }
}

答案 3 :(得分:0)

您需要允许您的5个线程进行通信,以便它们按严格的顺序运行。就像多米诺骨牌掉落一样,每个线程都必须等待,直到被戳破为止,然后它执行其操作并对下一个线程说:“现在可以走了”。

但是与多米诺骨牌摔倒不同,他们还必须重新站起来,以便为下一轮做好准备,最后一个多米诺骨牌必须再次击倒第一个多米诺骨牌!

我认为,这种通信的最佳内置方法是Phaser。这是使用Phasers的实现:

import java.util.concurrent.Phaser;
import java.util.concurrent.atomic.AtomicInteger;

public final class SequencedThreads
{
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        PrintThread[] threads = new PrintThread[5];

        // Create our 5 threads, each with a phaser waiting on itself 
        // and the previous thread
        for(int i = 0; i < 5; i++) {
            threads[i] = new PrintThread("T" + (i + 1));
            if(i > 0)
                threads[i - 1].next = threads[i].phaser;
        }

        // Join the last thread back to the first thread
        threads[4].next = threads[0].phaser;

        // Start our threads
        for(PrintThread pt : threads)
            pt.start();

        // Trigger the first thread to print
        threads[0].phaser.arriveAndDeregister();
    }

    private static final class PrintThread extends Thread {
        Phaser phaser;
        Phaser next;

        public PrintThread(String name) {
            super(name);
            this.phaser = new Phaser(2);
        }

        @Override
        public void run() {
            while(true) {
                // Block until we are poked
                phaser.arriveAndAwaitAdvance();
                int newCount = count.incrementAndGet();
                if(newCount > 10) {
                    // We are done, but trigger the other threads to allow 
                    // the JVM to exit
                    next.arriveAndDeregister();
                    return;
                }

                System.out.println(getName() + " -> " + newCount);
                // Pick ourselves back up
                phaser.register();
                // Poke the next domino
                next.arriveAndDeregister();
            }
        }
    }
}

答案 4 :(得分:0)

public static void main(String... args) {
    class LocalTask extends Thread {

        public static final int TOTAL = 5;

        private final int a;
        private final int b;
        private final AtomicInteger lock;

        public LocalTask(int id, AtomicInteger lock) {
            super("T" + id);
            a = id;
            b = id + TOTAL;
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    synchronized (lock) {
                        int count = lock.get();
                        String name = Thread.currentThread().getName();

                        if (count == a)
                            System.out.println(name + " -> " + a);
                        else if (count == b)
                            System.out.println(name + " -> " + b);
                        else
                            continue;

                        lock.incrementAndGet();
                        lock.notifyAll();

                        if (count == a)
                            lock.wait();
                        else
                            return;
                    }
                }
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    AtomicInteger lock = new AtomicInteger(1);

    for (int i = 1; i <= LocalTask.TOTAL; i++)
        new LocalTask(i, lock).start();
}