threadlocal笔记

发布时间:2024年01月15日

一个threadlocal只能存一个变量,但是一个线程可以存储多个threadlocal。

每个Thread都有自己的ThreadLocal.ThreadLocalMap实例。 map不是静态的,静态的是类定义。 ThreadLocalMap附加到Thread本身,而不是ThreadLocal对象。
当你调用 ThreadLocal.set(value) 时,它本质上是在做这样的事情:
Thread.currentThread().threadLocalMap.put(this, value);

每个Thread都有一个threadLocalMap,当线程销毁后,这个map也销毁了
每个Thread都有一个threadLocalMap而不是一个整数的原因是,这个map里面可以放很多东西,比如

ThreadLocal<Integer> localInt = new ThreadLocal<>();
ThreadLocal<String> localString = new ThreadLocal<>();


每个线程中就存储了一个字符串和一个整数:

Thread 1:
? ThreadLocalMap:
? ? localInt -> 42
? ? localString -> "Hello"

Thread 2:
? ThreadLocalMap:
? ? localInt -> 84
? ? localString -> "World"

在这种情况下,一个线程并不会有两个ThreadLocalMap,而是一个Map,这个Map有两个条目,一个整数相关的一个字符串相关的,键就是这两个ThreadLocal
但是多个线程有多个map,他们的map都有两个一样的key

Entry是map里的一个条目,一个Map说有多个Entry

静态内部类实际和顶层的类一样,如果我们把所有的静态内部类都放到顶层,大致如下:

class Entry extends WeakReference<ThreadLocal<?>> {
? ? ? ? ? ? /** The value associated with this ThreadLocal. */
? ? ? ? ? ? Object value;
? ? ? ? ? ? Entry(ThreadLocal<?> k, Object v) {
? ? ? ? ? ? ? ? super(k);
? ? ? ? ? ? ? ? value = v;
? ? ? ? ? ? }
}


class ThreadLocalMap {
? ? ? ? private Entry[] table;
}?

class Thread implements Runnable {
?? ?ThreadLocalMap threadLocals = null;
}


关于内存泄露,如果从不调用remove方法,而且Entry没有继承WeakReference,那么Entry就会强引用一个ThreadLocal,当一个线程是个长期工作线程或者使用线程池的时候,Thread强引用ThreadLocalMap,ThreadLocalMap强引用Entry[],就是代码里的table,table又强引用Entry,如果jdk袋面Entry强引用ThreadLocal,这样Entry里面的ThreadLocal和value一直存在,无法回收。如果是弱引用,那么ThreadLocal就会在gc的时候被回收,value不会立即被回收,后面如果在调用get set remove方法的时候因为Entry里面的ThreadLocal(可以类比成key)是空的,就会把value也清空:

private void remove(ThreadLocal<?> key) {
? ? Entry[] tab = table;
? ? int len = tab.length;
? ? int i = key.threadLocalHashCode & (len-1);

? ? for (Entry e = tab[i];
? ? ? ? ?e != null;
? ? ? ? ?e = tab[i = nextIndex(i, len)]) {
? ? ? ? if (e.get() == key) {
? ? ? ? ? ? e.clear();
? ? ? ? ? ? expungeStaleEntry(i);
? ? ? ? ? ? return;
? ? ? ? }
? ? }
}

如果调用remove方法,就会尽快的回收:

比如调试以下代码:

package learn.juc;

public class ThreadLocal3 {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();//没了演示没有设置final下面好设置为空
    public static void main(String[] args) {
        Runnable task = () -> {
            int random = (int) (Math.random() * 100);         
            threadLocal.set(random);

            try {
                Thread.sleep(2000);  
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            System.out.printf("Thread %s gets value: %d\n", Thread.currentThread().getName(), threadLocal.get());
            threadLocal=null;
            if(Thread.currentThread().getName().equals("Thread-1"))
	           while(true) {
	               try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
	               int i=1;//在这里打断点
	           }
        };

        for (int i = 0; i < 10; i++) {
            new Thread(task).start();
        }
    }
}

在int i=1这里打一个断点,可以看到:

就算你设置了threadlocal为空,过了5秒thread-1里面存储的54依然还在Thread里面,如果不调用get set remove不知道什么时候回收
如果加上remove方法的调用之后:
?

package learn.juc;

public class ThreadLocal3 {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();//for demo ,not final ,we can set it to null later    
    public static void main(String[] args) {
        Runnable task = () -> {
            int random = (int) (Math.random() * 100);         
            threadLocal.set(random);

            try {
                Thread.sleep(2000);  // simulate some work
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            System.out.printf("Thread %s gets value: %d\n", Thread.currentThread().getName(), threadLocal.get());
            //手动remove
            threadLocal.remove();
            if(Thread.currentThread().getName().equals("Thread-1"))
	           while(true) {
	               try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
	               int i=1;//just for a breakpoint
	           }
        };

        for (int i = 0; i < 10; i++) {
            new Thread(task).start();
        }
    }
}

同样的位置打断点,可以看到:
原来Thread-1里面的值86马上就没有了

文章来源:https://blog.csdn.net/2401_82381495/article/details/135554473
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。