Skip to content

Instantly share code, notes, and snippets.

@salvatorecapolupo
Created March 9, 2026 11:00
Show Gist options
  • Select an option

  • Save salvatorecapolupo/7783b761ce101ef5aceb426c1c57c4ff to your computer and use it in GitHub Desktop.

Select an option

Save salvatorecapolupo/7783b761ce101ef5aceb426c1c57c4ff to your computer and use it in GitHub Desktop.
import java.util.stream.IntStream;

public class FizzBuzz {
    public static void main(String[] args) {
        IntStream.rangeClosed(1, 100)
            .mapToObj(i -> (i % 15 == 0) ? "FizzBuzz" : (i % 3 == 0) ? "Fizz" : (i % 5 == 0) ? "Buzz" : i)
            .forEach(System.out::println);
    }
}

Questa è una sfida interessante. Di solito i thread sono fatti per correre in parallelo, ma qui vogliamo l'esatto opposto: una coordinazione millimetrica per mantenere l'ordine sequenziale.

Per scrivere meno codice possibile, la strategia migliore è usare un unico oggetto di sincronizzazione e un indice condiviso. Invece di creare classi complesse, useremo delle espressioni lambda.

Ecco una versione super compatta in Java:

public class FizzBuzzThreads {
    private static int counter = 1;
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Runnable task = () -> {
            while (true) {
                synchronized (lock) {
                    if (counter > 100) return;
                    
                    String out = (counter % 3 == 0 ? "Fizz" : "") + (counter % 5 == 0 ? "Buzz" : "");
                    System.out.println(out.isEmpty() ? counter : out);
                    
                    counter++;
                    lock.notifyAll();
                    try { if (counter <= 100) lock.wait(); } catch (InterruptedException e) {}
                }
            }
        };

        new Thread(task).start();
        new Thread(task).start();
    }
}

Perché questo approccio è efficiente?

  • Sincronizzazione minima: Usiamo un unico blocco synchronized su un oggetto lock.
  • Logica condivisa: Entrambi i thread eseguono lo stesso Runnable. Non serve scrivere logiche separate per "chi fa cosa".
  • Passaggio del testimone: Con lock.notifyAll() e lock.wait(), i thread si danno il cambio in modo educato. Appena uno finisce di stampare e incrementare, sveglia l'altro e si mette in pausa.
  • Concatenazione di stringhe: L'uso degli operatori ternari (counter % 3 == 0 ? "Fizz" : "") è il modo più breve in Java per gestire le condizioni del FizzBuzz senza una sfilza di if-else.

Come funziona il flusso

  1. Thread A entra nel blocco, stampa "1", incrementa il contatore a 2.
  2. Thread A sveglia tutti e va in wait().
  3. Thread B si sveglia, stampa "2", incrementa a 3, sveglia tutti e va in wait().
  4. Il ciclo continua fino a 100, dove il return interrompe l'esecuzione.

Nota: Se volessi essere ancora più estremo e usare 4 thread (uno per Fizz, uno per Buzz, uno per FizzBuzz e uno per i numeri), il codice triplicherebbe perché dovresti gestire condizioni di attesa molto più specifiche. Questa versione a 2 thread è il "punto dolce" tra multithreading e brevità.

Ti piacerebbe vedere come cambierebbe la struttura se volessimo usare esplicitamente un thread dedicato solo a "Fizz" e uno solo a "Buzz"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment