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);
}
}