ThreadLocal使用场景分析

一、什么是ThreadLocal

ThreadLocal是与线程绑定的一个变量。

二、ThreadLocal和Synchonized区别

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

通俗理解:向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。线程结束,map数据就销毁了。

三、ThreadLocal的使用场景

源码注释中有很清楚的解释:它是线程的局部变量,这些变量只能在这个线程内被读写,在其他线程内是无法访问的。 ThreadLocal 定义的通常是与线程关联的私有静态字段(例如,用户ID或事务ID)。

变量有局部的还有全局的,局部变量没什么好说的,一涉及到全局,那自然就会出现多线程的安全问题,要保证多线程安全访问,不出现脏读脏写,那就要涉及到线程同步了。而 ThreadLocal 相当于提供了介于局部变量与全局变量中间的这样一种线程内部的全局变量。

1、存储用户 Session。Session 的特性很适合 ThreadLocal ,因为 Session 之前当前会话周期内有效,会话结束便销毁。

2、线程内上线文管理器

3、数据库连接等

import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {

  //①使用ThreadLocal保存Connection变量
private static ThreadLocal connThreadLocal = new ThreadLocal();
public static Connection getConnection(){
         
        //②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
        //并将其保存到线程本地变量中。
if (connThreadLocal.get() == null) {
            Connection conn = ConnectionManager.getConnection();
            connThreadLocal.set(conn);
              return conn;
        }else{
              //③直接返回线程本地变量
            return connThreadLocal.get();
        }
    }
    public void addTopic() {

        //④从ThreadLocal中获取线程对应的
         Statement stat = getConnection().createStatement();
    }
}

将Connection的ThreadLocal直接放在Dao只能做到本Dao的多个方法共享Connection时不发生线程安全问题,但无法和其他Dao共用同一个Connection,要做到同一事务多Dao共享同一个Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

四、存在问题

1、内存泄漏
ThreadLocalMap 中使用的 key为弱应用,value 是强引用。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,ThreadLocal 的 key 也会被清理掉,但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

五、ThreadLocal源码

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

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

    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();
    }

    // 通过静态内部类实现变量与线程绑定
    static class ThreadLocalMap {...}

六、示例代码

public class ThreadClosure {

    /**
     * threadLocal变量,每个线程都有一个副本,互不干扰
     */
    public static ThreadLocal value = new ThreadLocal<>();

    @Test
    public void threadLocalTest() throws Exception {

        // 主线程设值
        value.set("这是主线程设置的123");
        String v = value.get();
        System.out.println("线程1执行之前,主线程取到的值:" + v);

        new Thread(() -> {
            String v1 = value.get();
            System.out.println("线程1取到的值:" + v1);
            // 设置 threadLocal
            value.set("这是线程1设置的456");

            v1 = value.get();
            System.out.println("重新设置之后,线程1取到的值:" + v1);
            System.out.println("线程1执行结束");
        }).start();

        // 等待所有线程执行结束
        Thread.sleep(5000L);

        v = value.get();
        System.out.println("线程1执行之后,主线程取到的值:" + v);

        new Thread(() -> {
            String s = value.get();
            System.out.println("线程2取到的值:" + s);
            value.set("789");
            String s1 = value.get();
            System.out.println("线程2取到的值:" + s1);
        }).start();

        String s = value.get();
        System.out.println("主线程取到的:" + s);

    }
}

结果:

线程1执行之前,主线程取到的值:这是主线程设置的123
线程1取到的值:null
重新设置之后,线程1取到的值:这是线程1设置的456
线程1执行结束
线程1执行之后,主线程取到的值:这是主线程设置的123
主线程取到的:这是主线程设置的123
线程2取到的值:null
线程2取到的值:789

你可能感兴趣的:(java基础)