Home > Back-end >  Two threads prints one character of a string twice, one by one?
Two threads prints one character of a string twice, one by one?

Time:06-26

I have troubles with the following task:

Both threads access the object of the class Print and print in reverse order the value of the variable type String, letter by letter, where each letter is printed with a hyphen (–). Example : Input = NAME. Output = E-E-M-M-A-A-N-N.

What I've done is the following:

public class Print implements Runnable {
    private String string;
    Print(String string) {
        this.string = string;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i = string.length()-1; i >= 0; i--) {
   
            System.out.print(string.charAt(i)   "-");
                
        }
            
    }
    
}

public class Task1 {
    public static void main(String[] args) {
        Print r = new Print("NAME");
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start(); 
    }
}

And it prints usually E-M-A-N-E-M-A-N, but it can be different. How do I make sure that it always prints the desired result? Thank you very much

CodePudding user response:

As I mentioned in the comment, you need CyclicBarrier, which will coordinate the two threads and not let one or the other run ahead.

However, if you don't want the last - character printed, there needs to be another coordination between the two threads for i == 0. One thread would print the last letter plus one -, then notify the other thread to go ahead and print just the last letter. This can be done with an atomic variable and a CountDownLatch. Who wins the update on the atomic variable will print letter-dash and count down the latch, the loser will wait on the latch and print the letter.

class Print implements Runnable {
    private String string;

    private CyclicBarrier cb = new CyclicBarrier(2);
    private CountDownLatch cdl = new CountDownLatch(1);
    private AtomicBoolean win = new AtomicBoolean();

    Print(String string) {
        this.string = string;
    }
    @Override
    public void run() {
        try {
            for (int i = string.length()-1; i >= 0; i--) {
                cb.await();

                if (i != 0) {
                    System.out.print(string.charAt(i)   "-");
                } else {
                    if (win.getAndSet(true)) {
                        cdl.await();
                        System.out.print(string.charAt(i));
                    } else {
                        System.out.print(string.charAt(i)   "-");
                        cdl.countDown();
                    }
                }
            }
        } catch (Throwable ex) {
           ex.printStackTrace();
        }
    }
}

Note that this setup works only for 2 threads.

CodePudding user response:

It prints usually E-M-A-N-E-M-A-N, but it can be different. How do I make sure that it always prints the desired result?

Without taking any additional steps, the output will vary each time. The first thread (t1) runs as quickly as it can, and the thread has no awareness that there maybe other instances of the Print Runnable.

To make sure it always prints the desired result, you need the threads to coordinate somehow. At a high level, the logic would look something like:

  • print a single letter ("E" on first pass through the loop, given input "NAME")
  • wait for all other threads to also print their letter – the correct way to do this is with one of several built-in synchronization options
  • once all threads agree that it's time to proceed, they can each proceed

A few options for how to proceed: use a CyclicBarrier or a Phaser.

Here's an example showing how to solving this by using a phaser.

First, this is the class for each thread. In the constructor, it accepts a Phaser, a List<String> (where each thread will add output), and the string text itself (ex: "NAME").

class CoordinatedRunnable implements Runnable {

    private final Phaser phaser;
    private final List<String> outputBuffer;
    private final String text;

    CoordinatedRunnable(Phaser phaser, List<String> output, String text) {
        this.text = text;
        this.phaser = phaser;
        this.outputBuffer = output;

        phaser.register();
        new Thread(this).start();
    }

    @Override
    public void run() {
        for (int i = text.length() - 1; i >= 0; i--) {
            outputBuffer.add(String.valueOf(text.charAt(i)));
            phaser.arriveAndAwaitAdvance();
        }
        phaser.arriveAndDeregister();
    }
}

Here is code for the caller, using text "NAME" and creating 2 threads coordinated by the same phaser.

Phaser phaser = new Phaser();
phaser.register();

List<String> outputBuffer = Collections.synchronizedList(new ArrayList<>());
String text = "NAME";
for (int i = 0; i < 2; i  ) {
    new CoordinatedRunnable(phaser, outputBuffer, text);
}

// main loop advances through each phase, along with each worker thread
for (int i = 0; i < text.length(); i  ) {
    phaser.arriveAndAwaitAdvance();
}

// reach here only when everyone is done with all phases
// print everything in the buffer, except for the last item
for (int i = 0; i < outputBuffer.size() - 1; i  ) {
    System.out.print(outputBuffer.get(i)   "-");
}

// print the last item by itself, no delimiter
System.out.print(outputBuffer.get(outputBuffer.size() - 1));

Here's the output:

E-E-M-M-A-A-N-N

And since the phaser is the only coordination point, it also works great with more than 2 threads. For example, here is the output with 10 threads:

E-E-E-E-E-E-E-E-E-E-M-M-M-M-M-M-M-M-M-M-A-A-A-A-A-A-A-A-A-A-N-N-N-N-N-N-N-N-N-N
  • Related