ThreadLocal应用及原理

在这里插入图片描述

1. 使用Demo

public class Test1 {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = 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() {
                    test1.setName(Thread.currentThread().getName()+"的数据");
                    System.out.println(Thread.currentThread().getName()+"的数据是"+test1.getName());
                }
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}
/*打印结果:
线程1的数据是线程1的数据
线程0的数据是线程0的数据
线程2的数据是线程1的数据 //线程2先改了自己的工作内存,又刷新到了主内存的name的值。(只是一种情况)
线程3的数据是线程3的数据
线程4的数据是线程4的数据*/

一点都不想解释,懂的都懂。
下面是使用ThreadLocal的demo:

public class Test1 {
    private String name;
    static ThreadLocal<String> tlname=new ThreadLocal<>();
    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() {
                    tlname.set(Thread.currentThread().getName()+"的数据");
                    System.out.println(Thread.currentThread().getName()+"的数据是"+tlname.get());
                    tlname.remove();
                }
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}
/*打印结果:
线程2的数据是线程2的数据
线程1的数据是线程1的数据
线程0的数据是线程0的数据
线程3的数据是线程3的数据
线程4的数据是线程4的数据*/

2. 运用场景

比如开启多个线程执行service代码,由于数据库要用到事务(数据库里面的事务,不是Java里面的事务),而事务的逻辑是要在service层体现的,如(多线程执行service层的transfer方法)
在这里插入图片描述
在这里插入图片描述
想到最直接的方法,就是把service层的connection传到dao层,为了保证传入的是自己的service里面申请的connection,所以这里上了锁。上锁肯定不好啊,全都锁上了为什么还要多线程。。所以用ThredLocal。

至于为什么要保证是同一个connection:随便说其中一点,假如共享connection的话,若第一个线程里面抛出了异常,断掉了connection,剩下的线程就不能用了,这合理吗?

采用ThreadLocal,service层便不需要向dao层传connection,降低耦合度。也比上一种加锁的方法性能好。因为都是同一个线程的,所以不需要传connection。

3. 源码


public class ThreadLocal<T> {

    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    protected T initialValue() { //ThreadLocalMap的value初始值为null
        return null;//protected修饰该方法很明显就是为了可以被子类重写
    }

    public ThreadLocal() {
    }
	//返回当前线程的map里面找到key==this对应的value,如果value=null,
	//也许是key不存导致vaule=null,也许是被回收了(这里挖坑)
	//则将<this,initialValue>加入map
    public T get() {
        Thread t = Thread.currentThread();//获取当前线程对象
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
	//将<this,initialValue>加入map,并返回initialVaule.
    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;
    }
	//将<this,value>加入到map
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
	//从map中将key==this的键值对移除
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

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

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

有几点比较重要的是:

  • 每一个线程有一个ThreadLocalMap,map里面存的是<ThreadLocal,value>。
  1. map里存储的entry数量变少,减少哈希冲突,现在键值对的个数取决于ThreadLocal的数量。假如是对每一个ThreadLocal维护一个<Thread,value>的map,键值对个数取决于Thread的数量,很明显Thread很可能远大于ThreadLocal的数量。
    2.当一个线程销毁后,对应的ThreadLocalMap会不被别的对象引用 ,就会被当成垃圾,进而回收掉。
  • ThreadLocalMap里面哈希冲突解决采用的方法是 开发地址-线性探测法.(d[i]=1)

4. 内存泄漏

在这里插入图片描述
在这里插入图片描述

ThreadLocalMap里的set/getEntry/remove方法中会调用expungeStaleEntry(),该方法会对key为null进行判断,key为null的话,也将value置为null。这意味着使用完ThreadLocal,CurrentThread已经运行的前提下,就算忘记调用remove方法,弱引用也比强引用可以多一层保障。弱引用的ThreadLocal会被回收,对应的value在下次一ThreadLocalMap调用set/get/remove任一方法时会被置为null,从而降低内存泄露的可能性。

  1. 采用弱引用了,也采用一层保障了,为什么还会内存泄漏呢?
    使用线程池的话:一个线程使用了很多ThreadLocal,key也都不为空,该线程的这个任务执行完了,继续调用下一个任务,直到线程被销毁才可以进行回收。(而实际中之后的任务可能都用不到了。。,这样会长期占用大量内存,导致对一些需要内存的线程来说内存不够用,从而内存溢出)
  2. 为什么Map存储value时不采用弱引用呢?
    这问题好智障。value只被map引用,下一次垃圾收集一发生,value所引用的以及value就被回收了。

🔗

ThreadLocal应用场景

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值