How does ThreadLocal implement thread isolation?

What happens when multiple threads modify the value in ThreadLocal?

We write a test code:

public class ThreadLocalTest {
    
    

    public static void main(String[] args) {
    
    
        ThreadLocal<String> sharedName = new ThreadLocal<>();
        ThreadLocal<Integer> sharedId = new ThreadLocal<>();

        //main线程最先修改
        System.out.println(Thread.currentThread().getName() + "初始值:" + sharedName.get()+" "+sharedId.get());
        sharedName.set("main");
        sharedId.set(103);
        System.out.println(Thread.currentThread().getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");


        Thread tA = new Thread() {
    
    

            @Override
            public void run() {
    
    
                System.out.println(this.getName() + "开始值:" + sharedName.get()+" "+sharedId.get());
                sharedName.set("线程A");
                sharedId.set(101);
                sleepTime(100);
                System.out.println(this.getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");
            }

        };
        Thread tB = new Thread() {
    
    
            @Override
            public void run() {
    
    
                sleepTime(100);
                System.out.println(this.getName() + "开始值:" + sharedName.get()+" "+sharedId.get());
                sharedName.set("线程B");
                sharedId.set(102);
                System.out.println(this.getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");
            }
        };
        tA.start();
        tB.start();
    }


    private static void sleepTime(long millis) {
    
    
        try {
    
    
            Thread.sleep(millis);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

}

Output result:

main初始值:null null
main设定后:main 103

Thread-0开始值:null null
Thread-0设定后:线程A 101

Thread-1开始值:null null
Thread-1设定后:线程B 102


The program has three threads main, tA, and tB, and all three threads have modified sharedName and sharedId (hereinafter referred to as name and id), but the name and id set by the main thread, tA and tB have not been read, ta and tB start read is null.

Why can't the tA thread read the name set by the main thread?

Look at the ThreadLoca.set() method:

//ThreadLocal.java
    public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            map.set(this, value);
        } else {
    
    
            createMap(t, value);
        }
    }

    ThreadLocalMap getMap(Thread t) {
    
    
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
    
    
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

//ThreadLocalMap是ThreadLocal的静态内部类
//ThreadLocalMap的构造器
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

It can be seen that the set method getMap()obtains the ThreadLocalMap of the current thread, and then stores the set string "main" in the ThreadLocalMap in the form of <key, value>. Looking at the source code of Thread below, we can see that each Thread has a member variable of type ThreadLocalMap.

public
class Thread implements Runnable {
    
    
	...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

Therefore, when saving, it is saved in the ThreadLocalMap of the current thread. How do you read it when you read it?
Take a look at the get() method:

    public T get() {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    private T setInitialValue() {
    
    
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            map.set(this, value);
        } else {
    
    
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
    
    
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
    
    protected T initialValue() {
    
    
        return null;
    }

It can be seen that when the get() method is read, it also reads the ThreadLocalMap of the current thread. So the name read by the tA thread is the ThreadLocalMap of the tA thread itself. At the beginning, the map is empty, so the name is null. After setting the value, it reads the value set by itself.

Obviously it is the same ThreadLocal object, why are different name values ​​obtained?
This is like two different HashMaps, mapA and mapB, both of which have a set of Entry with the same key value, but the value is different, so the read value is also different. The figure below is clear at a glance, tl is the abbreviation of ThreadLocal.

Conclusion: tA thread reads name, which is controlled by ThreadLocal. ThreadLocal obtains tA thread's own ThreadLocalMap, so tA thread also reads the name value of its own ThreadLocalMap. The name value set by the main thread cannot be read.

Guess you like

Origin blog.csdn.net/zhangjin1120/article/details/131466530