ThreadLocal 使用

什么是ThreadLocal

        顾名思义,本地线程共享变量,他可以使静态变量在每个线程中有一个镜像,互不干扰。使多线程开发时线程安全。实际实现是一个map,key是当前线程,value是你要存储的值。

 

简单看一下源码

threadlocal对外暴露的方法中,常用的有get(),set(),remove()三个。

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

set

先获取当前线程,然后把现成放入getMap方法中。看一下getMap:

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
ThreadLocal.ThreadLocalMap threadLocals = null;
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

获取传入线程的threadLocals变量,threadLocals类型是ThreadLocalMap。

回到set方法,如果map为空,创建一个新map,key为当前线程,value 为入参。

如果map不为空,用新值覆盖之前的值。

 

get

顾名思义,取值用的。

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

同样先获取当前线程,放入getMap获取到当前线程的threadLocals变量。

如果为空,则调用setInitialValue方法:

/**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }
    protected T initialValue() {
        return null;
    }

先获取一个null,再判断当前线程的threadLocals是否为空,如果为空,创建一个心的map,key为当前线程,value为null。返回。

如果不为空,用null覆盖之前的值。

简单说就是setInitialValue方法永远会返回一个null。并且重置当前现成的map的value为null。

回到get方法,如果map不为空,取出相应的值。

remove

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

简单易懂。不赘述了。

 

实践

写了一个entity

public class Session {

	private int id;
	private String name;
	
	
	
	public Session() {
		super();
	}
	public Session(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Session [id=" + id + ", name=" + name + "]";
	}

	
	
}

试验一下如果不使用ThreadLocal

public class ThreadLocal1 {

	static Session sessionstatic = new Session();
	static Long threadId = null;
	
	public Session getSession() {
		return sessionstatic;
	}

	public void setSession(Session session) {
		ThreadLocal1.sessionstatic = session;
	}
	
	
	void setThread() {
		threadId = Thread.currentThread().getId();
	}
	
	long getThread() {
		return threadId;
	}
	
	
	public static void main(String[] args) throws InterruptedException {
		ThreadLocal1 a = new ThreadLocal1();
		Session session = new Session();
		session.setId(1);
		session.setName("wm");
		
		a.setThread();
		a.setSession(session);
		System.out.println(a.getThread());
		System.out.println(a.getSession());
		
		Thread thread = new Thread(()->{
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ThreadLocal1 a2 = new ThreadLocal1();
			String name = a.getSession().getName();
			if(null==name) {
				throw new NullPointerException();
			}else{
				a2.setThread();
				a2.setSession(new Session(2,"tw"));
				System.out.println(a2.getThread());
				System.out.println(a2.getSession());
			}
		});
		thread.start();
		thread.join();
		
		
		System.out.println(threadId);
		System.out.println(sessionstatic.toString());
		
	}
}

静态变量的结果被第二次的覆盖了。

使用ThreadLocal

public class ThreadLocal2 {

	static ThreadLocal<Session> sessionstatic = new ThreadLocal<Session>();
	static ThreadLocal<Long> threadId = new ThreadLocal<Long>();
	
	void setThread() {
		threadId.set(Thread.currentThread().getId());
	}
	
	void setSession(Session value) {
		sessionstatic.set(value);
	}
	
	long getThread() {
		return threadId.get();
	}
	
	Session getSession() {
		return sessionstatic.get();
	}
	
	@Override
	public String toString() {
		return "session:"+sessionstatic.get() + "threadId:"+ threadId.get();
	}
	
	public static void main(String[] args) throws InterruptedException {
		ThreadLocal2 a = new ThreadLocal2();
		Session session = new Session();
		session.setId(1);
		session.setName("wm");
		
		a.setThread();
		a.setSession(session);
		System.out.println(a.getThread());
		System.out.println(a.getSession());
		
		
		Thread thread = new Thread(()->{
			System.out.println("thread:::::"+a.toString());
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ThreadLocal1 a2 = new ThreadLocal1();
			a2.setThread();
			a2.setSession(new Session(2,"tw"));
			System.out.println(a2.getThread());
			System.out.println(a2.getSession());
		});
		thread.start();
		thread.join();
		

		
		
		System.out.println(threadId.get());
		System.out.println(sessionstatic.get().toString());
		
	}
}

结果

打印出来的thread中的session和threadId都为null。因为前面的源码分析说了,这是一个新线程,新线程作为key取值时没有map的,它就会初始化一个map,key为新线程,value为null。

然后将a2的静态变量赋值为新线程的线程ID,session赋值为2,tw。

再回到main线程中打印原线程中的无变化。所以实现了线程之间的静态变量的隔离。保证了线程的安全。

 

说一个我的思考

这也是今天看项目时发现的一个问题,随手记录一下。

再分布式项目中,因为前台使用token传入后端,进行校验,然后去redis中取到当前用户的session,’session中存放我们需要的数据。现在如果没到取session这一步存放数据怎么办呢。比如存放token,其实好像就可以用ThreadLocal,他将token数据用ThreadLocal进行包装,保证每个线程的token是独立的,保证线程间的安全,token不会被其他线程篡改。我发现他就是这么用的,虽然我不太认同这个做法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值