Synchronized: cos'è, principio di funzionamento e utilizzo in Java

Autore: IT Sectr Pubblicato: 2026-03-19 Tempo di lettura: 8 min

Synchronized è un meccanismo di sincronizzazione integrato nel linguaggio Java che fornisce accesso esclusivo a sezioni critiche di codice. Secondo Oracle, 2024, il modificatore synchronized garantisce che un solo thread può eseguire il metodo o blocco marcato in un momento specifico. Questo meccanismo si basa sui monitor — un concetto fondamentale dei sistemi operativi che assicura il corretto funzionamento di applicazioni multithread di tutti i livelli di complessità.

Punti chiave

  • Synchronized — parola chiave Java per l'accesso thread-safe ai dati.
  • Monitor dell'oggetto — il meccanismo interno su cui si basa la sincronizzazione.
  • Metodo synchronized blocca l'intero metodo a livello di istanza o classe.
  • Blocco synchronized permette di sincronizzare solo una parte del codice.
  • Deadlock — uno dei principali problemi con la sincronizzazione annidata.

Cos'è synchronized?

Synchronized è una parola chiave in Java che garantisce che un solo thread esegua una sezione protetta di codice alla volta, prevenendo la corruzione dei dati durante l'accesso concorrente. È apparso nella prima versione di Java e rimane il modo più semplice per garantire la thread safety per sviluppatori di ogni livello.

Definizione e ruolo in Java

Il modificatore synchronized risolve due compiti: mutua esclusione e visibilità delle modifiche. Quando un thread esce da un blocco synchronized, tutte le modifiche sono garantite visibili ad altri thread che entrano in un blocco sincronizzato sullo stesso oggetto.

Synchronized può essere applicato a un intero metodo o a un blocco di codice arbitrario specificando un oggetto monitor. In entrambi i casi, la JVM inserisce le istruzioni monitorenter e monitorexit a livello di bytecode.

Motivi della sua nascita

Nelle applicazioni multithread senza sincronizzazione, si verifica una condizione di competizione (race condition) quando due thread modificano simultaneamente gli stessi dati, portando a risultati imprevedibili. Synchronized è diventato il primo e principale strumento di Java per combattere questo problema, fornendo una sintassi dichiarativa semplice accessibile a qualsiasi sviluppatore.

Come funziona synchronized?

Il meccanismo synchronized si basa sul concetto di monitor — un primitivo di sincronizzazione di alto livello incorporato in ogni oggetto Java. Il monitor viene associato a un oggetto quando si usa per la prima volta un blocco synchronized su di esso.

Monitor dell'oggetto

Ogni oggetto in Java ha un monitor associato. Quando un thread entra in un blocco synchronized, acquisisce il monitor dell'oggetto. Se il monitor è già occupato da un altro thread, il thread si blocca fino al rilascio. Nel bytecode, ciò corrisponde alla coppia di istruzioni monitorenter e monitorexit.

Stati di blocco (biased locking)

La JVM ottimizza synchronized attraverso diversi livelli: biased locking (blocco polarizzato) per accesso a thread singolo, lightweight locking (blocco leggero) per bassa contesa, e heavyweight locking (blocco pesante) per contesa intensa con coinvolgimento del SO. Questi livelli migliorano le prestazioni senza modificare il codice.

java
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

Regola happens-before

Synchronized stabilisce una relazione happens-before: tutte le azioni in un thread prima di uscire da un blocco synchronized sono visibili a un altro thread dopo essere entrato in un blocco sincronizzato sullo stesso oggetto. Questo garantisce non solo mutua esclusione ma anche consistenza dei dati per tutti i thread.

Metodo synchronized vs blocco

Java offre due modi per applicare synchronized: a livello di metodo e a livello di blocco. La scelta tra di essi influisce sulle prestazioni e sulla granularità della sincronizzazione.

Metodo synchronized

Marcare un metodo con il modificatore synchronized lo sincronizza automaticamente sull'istanza corrente (per i metodi di istanza) o sull'oggetto Class (per i metodi statici). È il modo più semplice per garantire la mutua esclusione, ma è spesso eccessivo se la sezione critica costituisce solo una piccola parte del metodo e il resto del codice non richiede sincronizzazione.

Blocco synchronized

Un blocco synchronized offre un controllo preciso: si specifica l'oggetto monitor e si sincronizza solo la sezione di codice necessaria, lasciando il resto del metodo fuori dal blocco. Questo minimizza il tempo di possesso del monitor e migliora le prestazioni complessive dell'applicazione in un ambiente multithread, poiché altri thread possono eseguire codice non correlato in parallelo senza attendere il rilascio del monitor.

java
class DataProcessor {
    private final Object lock = new Object();

    public void process() {
        // codice fuori dalla sezione critica - senza sincronizzazione
        prepareData()

        synchronized (lock) {
            // solo questo blocco è protetto
            updateSharedState()
        }

        // continuazione senza blocco
        cleanup()
    }
}
CriterioMetodo synchronizedBlocco synchronized
Monitorthis (istanza) o Classqualsiasi oggetto
Granularitàintero metodosolo il codice necessario
Leggibilitàaltamedia
Prestazioniinferiori per metodi grandisuperiori per sezioni critiche piccole

Synchronized in Android

Nello sviluppo Android, synchronized è ampiamente utilizzato per proteggere SharedPreferences, l'accesso al database e i componenti dell'interfaccia utente. Tuttavia, il suo utilizzo sul thread principale è fortemente sconsigliato a causa del rischio di congelamento dell'interfaccia.

Utilizzo con SharedPreferences

SharedPreferences in Android fornisce una sicurezza di base tra i thread, ma durante la modifica da più thread tramite Editor, potrebbe essere necessaria una sincronizzazione esterna. Un blocco synchronized con un oggetto di blocco separato garantisce la consistenza delle modifiche.

kotlin
class PreferencesManager(private val prefs: SharedPreferences) {
    private val lock = Any()

    fun writeToken(token: String) {
        synchronized (lock) {
            prefs.edit()
                .putString("auth_token", token)
                .apply()
        }
    }
}

Limitazioni nelle applicazioni Android

La limitazione principale di synchronized su Android è il blocco del thread. A differenza delle coroutine con Mutex, synchronized blocca completamente il thread di sistema. Sul thread principale, ciò causa ANR. Nello sviluppo Android moderno, si consiglia di sostituire synchronized con coroutine (suspend Mutex) o tipi atomici (AtomicInteger).

Alternative a synchronized

Java e Kotlin moderni offrono diverse alternative a synchronized, ognuna delle quali risolve gli stessi problemi con meno limitazioni o migliori prestazioni.

Lock da java.util.concurrent

L'interfaccia Lock con le implementazioni ReentrantLock e ReadWriteLock fornisce timeout, attesa interruptibile e multiple code Condition. È più flessibile di synchronized ma richiede il rilascio esplicito in finally, aumentando il rischio di errore se si dimentica l'unlock.

Classi atomiche

AtomicInteger, AtomicLong, AtomicReference e altre classi utilizzano algoritmi Lock-Free basati su CAS (Compare-And-Swap). Sono significativamente più veloci di synchronized in scenari di contesa moderata perché non bloccano i thread ma eseguono tentativi ottimistici senza richiedere il cambio di contesto del kernel del SO.

ThreadLocal e sicurezza dei thread

ThreadLocal fornisce un approccio alternativo: ogni variabile ThreadLocal è isolata all'interno di un singolo thread e non richiede sincronizzazione per lettura e scrittura. Ciò elimina completamente la necessità di synchronized per i dati che non devono essere condivisi tra i thread. ThreadLocal è attivamente utilizzato nei framework (Spring, Hibernate) per memorizzare il contesto delle transazioni e delle sessioni.

Coroutine e approccio compose

Nei progetti Kotlin per Android, un'alternativa a synchronized è Mutex di kotlinx.coroutines. Non blocca il thread del sistema operativo ma sospende la coroutine fino al rilascio del blocco — ciò consente un uso efficiente dei thread del pool e previene ANR durante le lunghe attese per il rilascio della risorsa.

kotlin
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()
var counter = 0

suspend fun safeIncrement() {
    mutex.withLock {
        counter++
    }
}

Prestazioni di synchronized

Le prestazioni di synchronized sono cambiate significativamente nelle versioni recenti di Java. Prima era considerato un meccanismo “pesante”, ma le JVM moderne hanno eliminato la maggior parte dell'overhead grazie alle ottimizzazioni avanzate del compilatore JIT. Vediamo in dettaglio come la macchina virtuale accelera il codice sincronizzato in fase di esecuzione.

Biased Locking e Lock Coarsening

Il compilatore JIT della JVM applica diverse ottimizzazioni: biased locking elimina la sincronizzazione se il blocco è sempre acquisito dallo stesso thread; lock coarsening unisce blocchi synchronized adiacenti in uno solo; lock elimination rimuove la sincronizzazione se l'oggetto è accessibile solo da un thread. Queste ottimizzazioni rendono synchronized praticamente gratuito in caso di bassa contesa.

Misurazione della contesa e scelta dell'ottimizzazione

La JVM determina il livello di contesa per ogni oggetto: quando non c'è contesa, viene attivato il biased locking; quando appare un secondo thread, il blocco passa alla modalità leggera con attesa spin; e solo durante l'attesa prolungata scala a pesante con un mutex di sistema. Questa escalation avviene automaticamente e lo sviluppatore non deve scegliere manualmente una strategia.

Confronto con Lock e classi atomiche

Nei benchmark moderni (Java 17+), synchronized mostra prestazioni comparabili a ReentrantLock con contesa bassa e moderata. Con contesa elevata, Lock può avere un vantaggio grazie a una coda di attesa più efficiente con supporto di timeout e interrupt. Per i sistemi ad alto carico dove la contesa è costante, ReentrantLock con modalità fair offre un comportamento più prevedibile.

Le classi atomiche (AtomicInteger, AtomicReference) rimangono le più veloci per contatori e flag semplici grazie all'implementazione Lock-Free basata su CAS. Non bloccano affatto i thread: in caso di conflitto, l'operazione si ripete semplicemente in un ciclo. Ciò fornisce un guadagno di prestazioni da 3 a 5 volte rispetto a synchronized sulle operazioni di incremento del contatore con 4-8 thread.

Domande frequenti

Qual è la differenza tra synchronized e volatile?

Synchronized fornisce sia mutua esclusione che visibilità. Volatile garantisce solo la visibilità delle modifiche — scrivere in una variabile volatile è visibile a tutti i thread ma non impedisce la modifica simultanea, quindi non protegge dalle condizioni di competizione.

Synchronized può causare deadlock?

Sì, un deadlock è possibile con sincronizzazione annidata usando ordini di monitor diversi. Ad esempio, un thread chiama synchronized(a) { synchronized(b) }, mentre un altro chiama synchronized(b) { synchronized(a) }. Evitate blocchi synchronized annidati o fissate un ordine di monitor consistente.

Cos'è un monitor in Java?

Un monitor è un meccanismo di sincronizzazione associato a ogni oggetto Java. Garantisce che un solo thread esegua codice synchronized su quell'oggetto. Il monitor include un blocco, una coda di attesa e un pool di thread in attesa di notifica tramite wait/notify.

Lock è più veloce di synchronized?

Nelle versioni moderne di Java (17+), synchronized non è inferiore a Lock in termini di prestazioni grazie alle ottimizzazioni JIT (biased locking, lock coarsening). Lock è preferito non per la velocità ma per le funzionalità aggiuntive: timeout, attesa interruptibile e multiple code Condition.

Come funziona synchronized con i metodi statici?

Un metodo statico synchronized utilizza il monitor dell'oggetto Class della classe specificata, non dell'istanza. Ciò significa che la sincronizzazione si applica a tutte le istanze della classe. I metodi non statici e statici synchronized utilizzano monitor diversi e non si bloccano a vicenda.

Riepilogo

  • Synchronized — meccanismo di sincronizzazione integrato di Java basato sui monitor.
  • Monitor dell'oggetto — struttura interna della JVM che garantisce la mutua esclusione.
  • Metodi wait/notify sono utilizzati solo all'interno di blocchi o metodi synchronized.
  • Blocco synchronized è preferibile a un metodo per la granularità di sincronizzazione più fine.
  • Happens-before garantisce la visibilità delle modifiche tra thread quando si sincronizza sullo stesso oggetto.
  • Deadlock — il rischio principale con sincronizzazione annidata usando ordine di monitor diverso.
  • Alternative — Lock, classi atomiche e coroutine con suspending Mutex.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche