Spring是如何处理Bean多线程下的并发问题的? ----- ThreadLocal

ThreadLocal天生为解决相同变量的访问冲突问题, 所以这个对于spring的默认单例bean的多线程访问是一个完美的解决方案。spring也确实是用了ThreadLocal来处理多线程下相同变量并发的线程安全问题。

1. ThreadLocal的简介

那么看看jdk是怎么说的:此类提供线程局部变量,这些变量与普通变量不同,每个线程都有自己的变量,通过ThreadLocal的get或者set方法访问,有独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段。

2.ThreadLocal的实现原理

ThreadLocal类中提供了几个方法:
1.public T get() { }
2.public void set(T value) { }
3.public void remove() { }
4.protected T initialValue(){ }

public T get()

    public T get() {
    
    
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals变量不为null,就可以在map中查找到本地变量的值
        if (map != null) {
    
    
            //根据ThreadLocal的弱引用的key 查询ThreadLocalMap的Value值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果threadLocals为null,则初始化当前线程的threadLocals变量
        return setInitialValue();
    }

    private T setInitialValue() {
    
    
        //此处是返回一个null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
        if (map != null)
            map.set(this, value);
            //如果threadLocals为null,说明首次添加,需要首先创建出对应的map
        else
            createMap(t, value);
        return value;
    }
    void createMap(Thread t, T firstValue) {
    
    
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

public void set(T value)

//话不多说,这个容易理解
   public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
        //这个是把ThreadLocal当做key,添加的属性值为value
            map.set(this, value);
        else
            createMap(t, value);
    }

public void remove()

使用完ThreadLocal要积极调用此方法,防止内存泄漏

  public void remove() {
    
    
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
         //当前线程根据ThrealLocal为key值删除
             m.remove(this);
     }

protected T initialValue()

对ThreadLocal初始化 使用时要自己重写此方法设置默认值

protected T initialValue() {
    
    
        return null;
    }

ThreadLocal的使用

1、我们先来看看jdk给的例子:

public class ThreadId {
    
    
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
    
    
        @Override
        protected Integer initialValue() {
    
    
            return nextId.getAndIncrement();
        }
    };

    public static int get() {
    
    
        return threadId.get();
    }
}
class TestID{
    
    
    public static void main(String[] args) {
    
    
        int i = ThreadId.get();
        System.out.println(i);//0
    }
}

执行此案例可以看到运行结果:是0 ,因为上面重写了initialValue() ,如果没有重写此方法,则返回结果为null。

2、验证线程的隔离性:

public class ThreadLocalDemo2 {
    
    
    public static ThreadLocal t1 = new ThreadLocal();
}

class ThreadA extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("TreadA " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class ThreadB extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("TreadB " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class Test {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ThreadA a = new ThreadA();
            ThreadB b = new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("Main " + (i + 1));
                System.out.println("Main get Value " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

执行此案例可以看到运行结果:ThreadA线程、ThreadB线程、和Main线程都各自获取自己线程存储的值。 但是调用的却是同一个ThreadLocal对象,那么得出结论,ThreadLocal可以实现线程间的隔离。

3、ThreadLocal的不可继承性:

由上面的例子可以看出,main线程和子线程间的数据是隔离的,并不能实现继承,那么ThreadLocal的子类InheritableThreadLocal提供了这种问题的解决方案。
下面我们通过一个案例了解一下:

public class ThreadLocalDemo6 {
    
    
    public static InheritableThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal() {
    
    
        @Override
        protected Long initialValue() {
    
    
            return new Date().getTime();
        }
    };
}
class ThreadA6 extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println("在ThreadA 线程中取值 : " + ThreadLocalDemo6.inheritableThreadLocal.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class Test6 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println("在线程main 中取值为: " + ThreadLocalDemo6.inheritableThreadLocal.get());
        }
        Thread.sleep(5000);
        ThreadA6 threadA6 = new ThreadA6();
        threadA6.start();
    }
}

执行此案例可以看到运行结果:main线程设置的属性值,在子线程中可以获取到,那么可以得出InheritableThreadLocal 具有继承的特性。

从ThreadLocalMap看ThreadLocal使用不当的内存泄漏问题

分析ThreadLocalMap内部实现

ThreadLocalMap的内部是一个Entry数组,下面我们来看一下entry的源码

    /**
     * Entry是继承自WeakReference的一个类,该类中实际存放的key是
     * 指向ThreadLocal的弱引用和与之对应的value值(该value值
     * 就是通过ThreadLocal的set方法传递过来的值)
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
        /** value就是和ThreadLocal绑定的 */
        Object value;

        //k:ThreadLocal的引用,被传递给WeakReference的构造方法
        Entry(ThreadLocal<?> k, Object v) {
    
    
            super(k);
            value = v;
        }
    }

由上可知,ThreadLocal是弱引用,那么这个引用抗不过一次GC,key可以被回收,那么value 确不能被回收,这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。

规范使用ThreadLocal

在使用ThreadLocal(set()或者get()) 后,如果不在使用则要及时使用remove()方法清理资源,避免内存泄漏的问题。

猜你喜欢

转载自blog.csdn.net/qq_43565087/article/details/106613341