欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ThreadLocal应用及原理

程序员文章站 2022-04-16 23:48:04
文章目录使用Demo运用场景源码使用Demopublic class Test1 { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public static void main(String[] args) { Tes....

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方法)
ThreadLocal应用及原理
ThreadLocal应用及原理
想到最直接的方法,就是把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. 内存泄漏

ThreadLocal应用及原理
ThreadLocal应用及原理

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应用场景

本文地址:https://blog.csdn.net/qq_42576687/article/details/109251846