9-threadLocal

ThreadLocal

  • 多个线程访问同一个共享变量时特别容易出现并发问题,特别是多线程需要对共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量的时候需要进行适当的同步,如图

    • 9-threadLocal
  • 同步的一般措施是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担,那么有没有一种方法可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal就可以做到。

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

    • 9-threadLocal
  • ThreadLocal使用实例

    package com.heiye.learn1;
    
    public class ThreadLocalTest {
        //print方法
        static void print(String threadName) {
            //打印当前线程本地内存中LocalVariable变量的值
            System.out.println(threadName + ":" + localVariable.get());
            //清除当前线程本地内存中的localVariable变量值
            //localVariable.remove();
        }
    
        //创建ThreadLocal变量
        static ThreadLocal<String> localVariable = new ThreadLocal<>();
    
        public static void main(String[] args) {
            //创建线程threadOne
            Thread threadOne = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程one变量localVariable值
                    localVariable.set("threadOne local Variable");
                    //调用打印函数
                    print("threadOne");
                    //打印本地变量值
                    System.out.println("threadOne remove after" + ":" + localVariable.get());
                }
            });
            //创建线程threadTwo
            Thread threadTwo = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程two变量localVariable值
                    localVariable.set("threadTwo local Variable");
                    //调用打印函数
                    print("threadTwo");
                    //打印本地变量值
                    System.out.println("threadTwo remove after" + ":" + localVariable.get());
                }
            });
    
            threadOne.start();
            threadTwo.start();
        }
    }
    
    • 9-threadLocal
    • 线程one首先通过set()方法为threadLocal变量设置了一个值,这其实设置的就是线程one本地内存中对于threadLocal变量的一个副本。这个副本是线程two访问不了的。
  • 如果清除当前线程本地内存中的localVariable变量值,也就是执行localVariable.remove();则:

    • 9-threadLocal

ThreadLocal实现原理

  • 首先查看一下ThreadLocal类图结构

    • 9-threadLocal
  • 由该图可知,Thread类有一个ThreadLocals和inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的HashMap。在默认的情况下,每个线程中的两个变量都为null,只有当第一个线程调用ThreadLocal的set或者get方法时才会创建它们,其实每个线程得到本地变量不是存放在ThreadLocal实例里面,而是存放在具体的线程内存空间里。ThreadLocal就是一个工具壳,它通过set方法把value值存放在调用线程的threadlocals里面并存放起来,当调用线程调用它的get()方法的时候,再从当前线程的threadLocals变量里面将其拿出来使用。

  • 分析set,get,remove逻辑

       //set
    	public void set(T value) {
            //获取当前线程
            Thread t = Thread.currentThread();
            //将当前线程作为key,到ThreadLocalMap取查找对应的线程变量
            ThreadLocalMap map = getMap(t);
            //如果找到,则设置
            if (map != null)
                map.set(this, value);
            else	//第一次调用就创建当前线程所在的hashmap
                createMap(t, value);
        }
    
    	//get
    	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();
        }
    
    	//remove
    	public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    
  • 在每一个线程内都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中的key为我们定义的threadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的内存变量ThreadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所有可能造成内存溢出。

上一篇:java基础之ThreadLocal


下一篇:10年阿里开发架构师经验分享,面试官轮轮虐你