zoukankan      html  css  js  c++  java
  • ThreadLocal的使用场景及实现原理

    1. 什么是ThreadLocal?

    线程局部变量(通常,ThreadLocal变量是private static修饰的,此时ThreadLocal变量相当于成为了线程内部的全局变量)

    2. 使用场景

    变量在线程内部共享,线程间无关

    再具体点,可以分为两类:

    • 单例的对象中static属性,线程内共享,线程间无关;
    • 工具类属性,线程内共享,线程间无关。

    为什么这么说呢?下面看4个问题:

    (1)对象为什么要是单例的?

    如果对象不是单例的,那么大可以每次都new一个对象,然后对用到属性赋值就行,代码如下:

    public class Service {
    
        private String key;
    
        void A() {
            // 代码实现,中间用到key
        }
    
        void B() {
            // 代码实现,中间用到key
        }
        
        // 省略get和set方法
    
    }
    

    在使用时,每个线程都new Service(),并对key赋值,然后调用其中的方法就行了,保证方法A和B用的key都是一个值。

    (2)单例对象的属性共享

    如果希望单例对象中的某个属性可以被共享,那么将属性声明为static就行了:

    public class Service {
    
        private static String key;
    
        // 省略其他方法
    
    }
    

    上面的实现确实保证了所有方法都能使用key,然而,在多线程环境下,key是不安全的。

    (3)单例对象在线程内属性共享,不同线程间相互不影响

    这就轮到ThreadLocal上场了:

    public class Service {
    
        private static ThreadLocal<String> key = new ThreadLocal<String>() {
            protected String initialValue() {
                return Thread.currentThread().getName();
            }
        };
    
        public void A() {
            System.out.println("methodA: " + key.get());
            key.set("methodA: " + key.get());
        }
    
        public void B() {
            System.out.println("methodB: " + key.get());
        }
    }
    

    使用方式:

    public class ThreadLocalTest {
    
        public static void main(String[] args) {
    
            final Service service = new Service(); //模拟单例对象的使用
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.A();
                    service.B();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.A();
                    service.B();
                }
            }).start();
    
        }
    
    }
    

    运行结果:

    methodA: Thread-0
    methodB: methodA: Thread-0
    methodA: Thread-1
    methodB: methodA: Thread-1
    

    (4)工具类中线程共享,线程间无关

    工具类的代码:

    public final class XUtil {
    
        private static ThreadLocal<String> key = new ThreadLocal<String>();
    
        private XUtil() {
        }
    
        public static void A() {
            // 实现
        }
    
        public static void B() {
            // 实现
        }
    
    }
    

    在使用XUtil时,每个线程中key可以使用,不同线程间不受影响。

    3. ThreadLocal的实现原理

    为什么一个static的变量(即:类变量)可以做到:线程内共享,线程间无关?

    看下ThreadLocal中的get源码:

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

    关键就在getMap()方法:

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

    取的是当前线程内部的threadLocals属性。

    查看Thread类:

    ThreadLocal.ThreadLocalMap threadLocals = null;
    

    threadLocals是ThreadLocal类中自定义的一个HashMap类。

    原来数据就存在当前线程内部,自然就能做到线程内共享,线程间无关了。

    接着看下set的源码:

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

    无论是map.set(this, value)还是createMap(t, value),最后都是将数据保存到当前线程中的那个HashMap中:将ThreadLocal变量作为key,value就是要保存的数据。

    4. ThreadLocal的内存泄露

    在前面看到数据最终是存在线程内部的一个Map中的:

    ThreadLocal.ThreadLocalMap threadLocals = null;
    

    且key是ThreadLocal变量的引用,在get方法:

    ThreadLocalMap.Entry e = map.getEntry(this); // this为当前对象的引用
    

    当ThreadLocal变量被销毁时,而当前线程又持有ThreadLocal的引用,那么ThreadLocal就不会被回收,导致内存泄露。

    然而,编写JDK的大牛们考虑到了这个问题,因此将ThreadLocalMap的key设置为弱引用

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal> {
            Object value;
            
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
    }
    

    对ThreadLocal变量的弱引用,在GC时,ThreadLocal变量就会被回收。

    于是,在当前线程的本地变量HashMap中,原来ThreadLocal作为key的,现在变成null作为key了,该key-value变得不可访问了,如果当前线程一直不结束,那么value对应的对象就无法释放,也就是发生内存泄露了。

    参考文献:

    《深入分析 ThreadLocal 内存泄漏问题》

  • 相关阅读:
    jquery datatable后台分页、移动端隐藏列、自定义列显示格式
    今日热门文章
    深入理解STL源码 系列文章
    LED显示屏通讯协议 2
    LED显示屏通讯协议 1
    定制的小键盘输入数字显示的LED计分显示屏
    Sql语句中的LIKE关键字中通配符以及Escape关键字
    Virtual Box扩展虚拟硬盘的问题
    选择腾讯云部署应用的要慎重了,私有网络阉割,可靠性变得难以确定!
    rabbitmq 重置
  • 原文地址:https://www.cnblogs.com/acode/p/8796626.html
Copyright © 2011-2022 走看看