Analyse du code source de ThreadLocal et utilisation de base

ThreadLocal permet d'isoler des données entre threads. Lorsque plusieurs threads doivent gérer une même propriété non thread-safe, ThreadLocal garantit son atomicité. Cas typique : variables dans les algorithmes DFS.

Principe de la méthode set()

public void set(T valeur) {
    Thread t = Thread.currentThread();
    ThreadLocalMap carte = getMap(t);
    if (carte != null)
        carte.set(this, valeur);
    else
        createMap(t, valeur);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
class Entry extends WeakReference<ThreadLocal<?>> {
    Object valeur;
    
    Entry(ThreadLocal<?> cle, Object v) {
        super(cle);
        valeur = v;
    }
}
private void set(ThreadLocal<?> cle, Object valeur) {
    Entry[] table = this.table;
    int taille = table.length;
    int index = cle.threadLocalHashCode & (taille-1);
    
    for (Entry e = table[index]; e != null; e = table[index]) {
        ThreadLocal<?> k = e.get();
        
        if (k == cle) {
            e.valeur = valeur;
            return;
        }
        
        if (k == null) {
            remplacerEntrieObsolete(cle, valeur, index);
            return;
        }
        index = indexSuivant(index, taille);
    }
    
    table[index] = new Entry(cle, valeur);
    if (++size >= seuil)
        rehasher();
}

Principe de la méthode get()

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap carte = getMap(t);
    if (carte != null) {
        Entry e = carte.getEntry(this);
        if (e != null)
            return (T)e.valeur;
    }
    return definirValeurInitiale();
}
private Entry getEntry(ThreadLocal<?> cle) {
    int index = cle.threadLocalHashCode & (table.length-1);
    Entry e = table[index];
    return (e != null && e.get() == cle) ? e : 
        getEntryApresEchec(cle, index, e);
}

Stockage mémoire

Les instances ThreadLocal et leurs valeurs sont stockées dans le tas (heap). La visibilité est limitée au thread via des mécanismes enternes.

Exemple d'utilisation

public class ExempleThreadLocal {
    private static ThreadLocal<Integer> compteurThread = 
        ThreadLocal.withInitial(() -> 0);

    public int incrementer() {
        compteurThread.set(compteurThread.get() + 1);
        return compteurThread.get();
    }

    static class Tache extends Thread {
        private ExempleThreadLocal exemple;

        public Tache(ExempleThreadLocal e) {
            this.exemple = e;
        }

        public void run() {
            for(int i=0; i<3; i++) {
                System.out.println(
                    "Thread [" + getName() + "] - Compteur: " + 
                    exemple.incrementer()
                );
            }
        }
    }

    public static void main(String[] args) {
        ExempleThreadLocal exemple = new ExempleThreadLocal();
        new Tache(exemple).start();
        new Tache(exemple).start();
        new Tache(exemple).start();
    }
}

Références faibles et fuites mémoire

Les clés Entry utilisent des références faibles. Si ThreadLocal est garbage collecté, la clé devient null mais la valeur persiste si le thread vit longtemps, causant des fuites mémoire.

Solution : Appeler remove() après utilisation

private void remove(ThreadLocal<?> cle) {
    Entry[] table = this.table;
    int taille = table.length;
    int index = cle.threadLocalHashCode & (taille-1);
    
    for (Entry e = table[index]; e != null; e = table[index]) {
        if (e.get() == cle) {
            e.clear();
            supprimerEntrieObsolete(index);
            return;
        }
        index = indexSuivant(index, taille);
    }
}

Étiquettes: Java ThreadLocal Concurrence FuitesMémoire WeakReference

Publié le 18 juillet à 05h01