Search code examples
javamultithreadingrunnablesynchronizedjava.util.concurrent

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


I want to write a multithread app that prints characters from Strings one by one and after first "round" it would preserve order for the other rounds. It should work somehting like this:

For Strings:

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

It would print:

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

or maybe

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

depending on which proccess started first in the very first round

My solution so far looks like this

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

But it doesn't preserve the order of letters written to the console and also i fill the Strings to the some hardcoded value right now, but i would want to make it work properly without equal strings. Any suggestions on this?


Solution

  • Since you're asking for a solution with CyclicBarrier, here's a way you could do this with one... It definitely wouldn't be my first thought for how to solve the issue though (assuming the issue isn't 'do this with a CyclicBarrier'...).

    • Create a CyclicBarrier of length 4.
    • Assign each Thread a number (0 to 3) when it starts (using an AtomicInteger or otherwise).
    • Have each Thread do something like:

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

    I.e. each Thread spins until the number of waiting parties is equal to that Thread's number.

    Whichever is assigned 0 will always go through first, while all the others are stuck spinning. Once that Thread has done its StringBuilder thing, it will then await, which in turn frees the Thread assigned 1 to go through. The order will stay consistent after the number assignments.


    To get the unique id per process, a simple AtomicInteger can be used.

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