The easiest way is to have a mutable variable from which each stream reads and updates in accordance with its rotation, otherwise it simply waits for its rotation. When counter is 100 , you stop all threads to start, breaking the outer loop.
class MyRunnable implements Runnable { private static final int LIMIT = 20; private static volatile int counter = 0; private int id; public MyRunnable(int id) { this.id = id; } @Override public void run() { outer: while(counter < LIMIT) { while (counter % NB_THREADS != id) { if(counter == LIMIT) break outer; } System.out.println("Thread "+Thread.currentThread().getName()+ " printed " + counter); counter += 1; } } }
Given a LIMIT of 20 and 10 threads, it produces:
Thread 0 printed 0 Thread 1 printed 1 Thread 2 printed 2 Thread 3 printed 3 Thread 4 printed 4 Thread 5 printed 5 Thread 6 printed 6 Thread 7 printed 7 Thread 8 printed 8 Thread 9 printed 9 Thread 0 printed 10 Thread 1 printed 11 Thread 2 printed 12 Thread 3 printed 13 Thread 4 printed 14 Thread 5 printed 15 Thread 6 printed 16 Thread 7 printed 17 Thread 8 printed 18 Thread 9 printed 19
Of course, this is a very poor use of multithreading, because each thread is waiting in line for printing and increasing the counter.
Multithreading works well when threads can run independently for a relatively long time, and then can sometimes be found to compare or combine their results, if necessary.
For example, in the fork-join model, each thread performs its task independently, then their results are combined to obtain the final result, for example, in a merge sort, for example. But this assumes that the task can be easily parallelized into independent sub-tasks, which is not the case here, because your final result should be consecutive numbers.
So here a simple cycle will be much more efficient, but I can understand it for training purposes.