ThreadLocal的理解

一、ThreadLocal和synchronized的区别

1、分析以下代码

package com.xie.threadlocal;

public class Test1 {
    private String name;

    public void setName(String name){
        this.name=name;
    }

    public String getName(){
        return name;
    }

    public static void main(String[] args) {
        Test1 test1=new Test1();
        for (int i=0;i<5;i++){
            Thread thread = new Thread(() -> {
                test1.setName(Thread.currentThread().getName()+"的数据");
                System.out.println("==================");
                System.out.println(Thread.currentThread().getName()+"--->"+test1.getName());
            });
            thread.setName(String.valueOf("线程的"+i));
            thread.start();
        }
    }
}

结论:线程与线程之间会相互影响

2、如何解决上面的线程互相影响?

①使用ThreadLocal

package com.xie.threadlocal;

public class Test1 {
    ThreadLocal<String> name=new ThreadLocal<>();

    public void setName(String name){
        this.name.set(name);
    }

    public String getName(){
        return this.name.get();
    }

    public static void main(String[] args) {
        Test1 test1=new Test1();
        for (int i=0;i<5;i++){
                Thread thread = new Thread(() -> {
                    test1.setName(Thread.currentThread().getName()+"的数据");
                    System.out.println("==================");
                    System.out.println(Thread.currentThread().getName()+"--->"+test1.getName());
                });
            thread.setName(String.valueOf("线程的"+i));
            thread.start();
        }
    }
}

②使用synchronized

package com.xie.threadlocal;

public class Test1 {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        for (int i = 0; i < 5; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (Test1.class) {
                        test1.setName(Thread.currentThread().getName() + "的数据");
                        System.out.println("==================");
                        System.out.println(Thread.currentThread().getName() + "--->" + test1.getName());
                    }
                }
            });
            thread.setName(String.valueOf("线程的" + i));
            thread.start();

        }
    }
}

1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

3、Synchronized是时间换空间;ThreadLocal是空间换时间。

二、ThreadLocal的内部结构

JDK8之前

JDK8之后,Thread和ThreadLocal位置换了

 

 这样设计的好处:

        ①存储更少的Entry

        ②当Thread销毁的时候,ThreadLocalMap也会被销毁

三、ThreadLocal的方法(原码)

set

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            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();
    }



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);
        return value;
    }

remove

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

四、ThreadLocalMap的基本结构

五、ThreadLocal的内存泄漏问题

1、ThreadLocalMap的强依赖

我们最常用的直接new一个对象或者通过反射创建一个对象,都是强依赖;对于强依赖,有一个很关键的点,就是如果jvm内存不足的时候,就算要报异常也不会回收这一部分内存。

把应用jvm启动内存调小到1m,然后运行一下代码:

public static void main(String[] args) {

 String str = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";

 List<String> list = new ArrayList<>();

 for(int i = 0;i < 100000;i ++) {

 list.add(i + str);

 }

 System.out.println(list.size());

}

2、ThreadLocalMap的弱依赖

弱引用相对软引用,依赖强度又弱了一些;而弱引用和软引用的区别是,每次垃圾回收都会回收掉弱引用持有的对象。

public static void main(String[] args) throws Exception {

 User u = new User();

 WeakReference<User> studentWeakRef = new WeakReference<>(u);

 u = null;

 System.out.println(studentWeakRef.get());

 System.gc();

 System.out.println("After Gc:");

 System.out.println(studentWeakRef.get());//gc之后一定会被回收

}

3、ThreadLocalMap为什么要使用弱依赖

在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏.

4、如何正确的使用ThreadLocal
①将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
②每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值