Java 深入理解ThreadLocal

一、ThreadLocal概述

ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题

举例

package lockTest;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/23 17:15
 */

public class ThreadLocalTest {

    static ThreadLocal<Dog> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                threadLocal.set(new Dog("Aiguodala"));
                System.out.println(Thread.currentThread().getName()+ " : " + threadLocal.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName()+ " : "  + threadLocal.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

class Dog {

    String name;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }

    public Dog(String name) {
        this.name = name;
    }
}

在这里插入图片描述

  • 第一个线程可以取出自己set的值
  • 而第二个线程取不出
  • 说明ThreadLocal 对于线程来说是隔离的

二、原理解析

首先应该清楚Thread 类有两个重要的字段,分别用来维护每个Thread线程自己的ThreadLocalMap,具体的应用会在下面分析

    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

1. set( ) 方法

    public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 通过当前线程获取到一个ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        // 然后对每一个线程独自维护的ThreadLocalMap 进行操作
        if (map != null)
        	// key是当前ThreadLocal value 是放置进入的值
            map.set(this, value);
        else
            createMap(t, value);
    }
        private void set(ThreadLocal<?> key, Object value) {
        
            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)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

set 其实就是将数值放置在ThreadLocalMap 中的 private Entry[] table 数组中 ,观看源码可知,Entry继承于WeakReference,所以是弱引用,那为什么要将它设置成弱引用呢?

        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

原因其实很简单,例如,当构造出该ThreadLocal 类的线程将该连接ThreadLocal 的引用断开,此时,说明该ThreadLocal 已经没用,应该被垃圾回收,但是,该ThreadLocal作为key,将引用存在于该Entry数组中以至于迟迟不能回收,将造成内存泄漏。但是如果设置为弱引用,一旦与该ThreadLocal 相连的强引用被中断,那么在下一次垃圾回收则会无视弱引用而对该ThreadLocal 进行回收。

与此同时,因为该ThreadLocal被回收,此时这对键值对的key为null,则无法通过key访问该value,也会导致内存泄漏,所以一旦不使用,建议调用remove() 方法。

2. get() 和 remove()

理解了上面内容。则对于get() 和 remove() 则也不难理解,就不多赘述,就是从Entry数组中获取相关元素或者删除相关元素的操作

    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();
    }
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
        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;
                }
            }
        }

三、InheritableThreadLocal 概述

读懂了上面的知识,很容易发现ThreadLocal 是线程独立的,但是,如果我想共享ThreadLocal 的数据应该怎么办呢,这时候InheritableThreadLocal 就应运而生,InheritableThreadLocal 可以支持子线程访问到父线程的放置其中的数据

举例:

package lockTest;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/23 17:15
 */

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
        InheritableThreadLocal<String> stringInheritable = new InheritableThreadLocal<>();

        // 主线程赋对上面两个变量进行赋值
        stringThreadLocal.set("this is threadLocal");
        stringInheritable.set("this is inheritableThreadLocal");

        // 创建线程
        Thread thread1 = new Thread(()->{
            // 获得ThreadLocal中存放的值
            System.out.println(stringThreadLocal.get());

            // 获得InheritableThreadLocal存放的值
            System.out.println(stringInheritable.get());
        });

        thread1.start();
    }


}

在这里插入图片描述

  • 分别在ThreadLocal 中和 InheritableThreadLocal 中放置了数据
  • 最终子线程只能获取到InheritableThreadLocal 中放置的值

四、原理解析

InheritableThreadLocal 继承自 ThreadLocal 并重写了它下面三个方法,当调用getMap () 或者 createMap()的时候就不是给threadLocal赋值,而是给Thread的inheritableThreadLocals 字段赋值

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

那么,这个inheritableThreadLocals 又是如何传递的呢?

当创建线程的时候(以博主的例子,主线程为例)

先调用了无参构造,里面又会不断的调用重载的init方法

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

有兴趣的可以都看看线程初始化的过程,但是对于inheritableThreadLocals 来说,重要的是这几句

		// 这里的currentThread 指的是创建该线程的线程
		Thread parent = currentThread();
		
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

传入的参数inheritThreadLocals 为true 并且如果父线程的inheritableThreadLocals 不为空,那么在createInheritedMap内部会使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量,相当于将父线程的inheritableThreadLocals传递给了子线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值