首页 > 解决方案 > 如何使用 CyclingBarrier 同步线程并保持它们的执行顺序?

问题描述

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

对于字符串:

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

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

标签: javamultithreadingrunnablesynchronizedjava.util.concurrent

解决方案


既然您要求使用 的解决方案CyclicBarrier,这里有一种方法可以用一个来做到这一点......但我绝对不会首先想到如何解决这个问题(假设问题不是“用CyclicBarrier'...)。

  • 创建一个CyclicBarrier长度为 4 的。
  • 在开始时(使用 an或其他方式)为每个人分配Thread一个数字(0 到 3)。AtomicInteger
  • 让每个人都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;
            }
        }
    };
}

推荐阅读