如何使用 CyclingBarrier 同步线程并保留它们的执行顺序?

How to synchronise threads and preserve their execution order with CyclingBarrier?

我想编写一个多线程应用程序,逐个打印字符串中的字符,并且在第一个 "round" 之后它会保留其他轮次的顺序。它应该像这样工作:

对于字符串:

private String[] strings = {"aaaa", "bb", "ccccccccccccc", "dddddd"};

它将打印:

abcd abcd acd acd cd cd c c c c c c c

或者也许

dbac dbac dac dac dc dc c c c c c c c

取决于第一轮最先启动的进程

目前我的解决方案是这样的

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Printer {

    private CyclicBarrier cyclicBarrier;

    private final static String one = "aaa";
    private final static String two = "bbbb";
    private final static String three = "c";
    private final static String four = "dddddd";

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.runSimulation(4);
    }

    private void runSimulation(int numberOfStrings) {
        cyclicBarrier = new CyclicBarrier(numberOfStrings, new AggregatorThread());

        Thread thread = new Thread(new PrintingThread(padSpaces(one, 10)));
        Thread thread1 = new Thread(new PrintingThread(padSpaces(two, 10)));
        Thread thread3 = new Thread(new PrintingThread(padSpaces(three, 10)));
        Thread thread4 = new Thread(new PrintingThread(padSpaces(four, 10)));
        thread.start();
        thread1.start();
        thread3.start();
        thread4.start();
    }

    class AggregatorThread implements Runnable{
        @Override
        public void run() {
            System.out.print("  ");
        }
    }

    class PrintingThread implements Runnable{

        private String toPrint;
        private int iterator;

        public PrintingThread(String toPrint) {
            this.toPrint = toPrint;
            this.iterator = 0;
        }

        @Override
        public void run() {
            while(iterator < toPrint.length()) {
                System.out.print(toPrint.charAt(iterator));
                iterator++;
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String padSpaces(String inputString, int length) {
        if (inputString.length() >= length) {
            return inputString;
        }
        StringBuilder sb = new StringBuilder();
        while (sb.length() < length - inputString.length()) {
            sb.append(' ');
        }
        StringBuilder sb1 = new StringBuilder(inputString);
        sb1.append(sb);

        return sb1.toString();
    }
}

但它不保留写入控制台的字母顺序,而且我现在将字符串填充为一些硬编码值,但我想让它在没有相等字符串的情况下正常工作。 对此有何建议?

既然你要用 CyclicBarrier 寻求解决方案,这里是 a 你可以用一个来解决这个问题的方法......这绝对不是我的第一个不过考虑了如何解决问题(假设问题不是 'do this with a CyclicBarrier'...)。

  • 创建长度为 4 的 CyclicBarrier
  • 在开始时(使用 AtomicInteger 或其他方式)为每个 Thread 分配一个 数字 (0 到 3)。
  • 让每个 Thread 做如下事情:

    while (barrier.getNumberWaiting() != this.threadNumber) {
    }
    
    // Do your adding to the StringBuilder here...
    
    barrier.await();
    

即每个 Thread 旋转直到等待方的数量等于 Thread 的数量。

分配为 0 的总是先通过,而其他所有的都卡在旋转中。一旦 Thread 完成了它的 StringBuilder 事情,它就会 await,这反过来又释放了 Thread 分配的 1 通过。编号分配后顺序将保持一致。


要获得每个进程的唯一 ID,可以使用简单的 AtomicInteger

private final AtomicInteger idCounter = new AtomicInteger();
private final CyclicBarrier barrier = new CyclicBarrier(4);
private final AtomicInteger doneCounter = new AtomicInteger();

public Runnable createRunnable() {
    return () -> {
        final int threadId = this.idCounter.getAndIncrement();

        boolean threadDone = false;
        boolean moreCharacters = true;
        while (true) {
            while (this.barrier.getNumberWaiting() != threadId) {
            }

            // Add to StringBuilder here...

            // Set the 'moreCharacters' flag as false once this thread
            // has handled its String.
            // They will still need to spin though, to make sure the
            // parties waiting keep adding up as appropriate.

            if (!moreCharacters && !threadDone) {

                // 'threadDone' used so that each thread only
                // increments the 'doneCounter' once.

                this.doneCounter.incrementAndGet();
                threadDone = true;
            }

            barrier.await();

            if (this.doneCounter.get() == 4) {
                // Exit out of the loop once all Threads are done.
                break;
            }
        }
    };
}