likes
comments
collection
share

Java进阶 ——— Java多线程(四)之多线程局部变量TreadLocal

作者站长头像
站长
· 阅读数 11

在了解ThreadLocal之前,一定要确定一个概念:ThreadLocal不是用来解决共享对象的多线程访问问题的 那么ThreadLocal在多线程的作用是什么呢?从下面几个方面来了解

ThreadLocal的作用

ThreadLocal可以理解为:线程局部变量, 是每一个线程所单独持有的。其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联,

在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。

当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。

深入了解ThreadLocal使用

先不去分析源码,仅仅写个例子,根据例子学习ThreadLocal的简单使用。

public class ThreadTest {
  //定义一个全局ThreadLocal
	public static ThreadLocal<String> locals= new ThreadLocal<String>();
  
  //开启三个线程
	public void threadLocalTest(){
		ThreadLocalTest test1 = new ThreadLocalTest("AA");
		ThreadLocalTest test2 = new ThreadLocalTest("BB");
		ThreadLocalTest test3 = new ThreadLocalTest("CC");
		test1.start();
		test2.start();
		test3.start();
	}
}

在线程里往ThreadLocal塞值,再打印出

public class ThreadLocalTest extends Thread {

	private int a = 5;

	public ThreadLocalTest(String name) {
		super(name);
	}

	@Override
	public void run() {
		super.run();
		try {
			for (int i = 0; i < 5; i++) {
				a += 1;
				ThreadTest.locals.set(a + "");
				Log.e(this.getName(), "value ===== " + ThreadTest.locals.get());
				Thread.sleep(300);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

查看运行结果

10-24 10:31:46.340 9307-9350/com.t9.news E/AA: value ===== 6
10-24 10:31:46.340 9307-9351/com.t9.news E/BB: value ===== 6
10-24 10:31:46.343 9307-9352/com.t9.news E/CC: value ===== 6
10-24 10:31:46.640 9307-9350/com.t9.news E/AA: value ===== 7
10-24 10:31:46.641 9307-9351/com.t9.news E/BB: value ===== 7
10-24 10:31:46.644 9307-9352/com.t9.news E/CC: value ===== 7
10-24 10:31:46.940 9307-9350/com.t9.news E/AA: value ===== 8
10-24 10:31:46.941 9307-9351/com.t9.news E/BB: value ===== 8
10-24 10:31:46.944 9307-9352/com.t9.news E/CC: value ===== 8
10-24 10:31:47.240 9307-9350/com.t9.news E/AA: value ===== 9
10-24 10:31:47.241 9307-9351/com.t9.news E/BB: value ===== 9
10-24 10:31:47.244 9307-9352/com.t9.news E/CC: value ===== 9
10-24 10:31:47.540 9307-9350/com.t9.news E/AA: value ===== 10
10-24 10:31:47.541 9307-9351/com.t9.news E/BB: value ===== 10
10-24 10:31:47.545 9307-9352/com.t9.news E/CC: value ===== 10

看到每个线程的里都有自己的String,并且互不影响----,不存在一个线程修改另一个线程中值得情况,对于同一个ThreadLocal对象而言,内部数据仅为自己独有,其他线程无法修改

ThreadLocal的理解

了解了ThreadLocal的使用,接下来肯定要看看源码,分析内部实现方式。

就从ThreadLocal 几个主要方法来学习

public T get()
public void set(T value) 
public void remove()

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本

首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

先来看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();
        //然后getMap(t)方法获取到一个map,类型为ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //不为空
        if (map != null) {
        //然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            //如果获取成功,则返回value值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //否则调用setInitialValue方法返回value
        return setInitialValue();
    }

仔细看看每一步的操作:

  • getMap(t)
/**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
    //返回当前线程中的成员变量threadLocals
        return t.threadLocals;
    }

那么继续查看,进入Thread.class ,成员变量threadLocals是什么

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值

如果getMap为null,则返回setInitialValue()

  • 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);
        //就是如果map不为空,就设置键值对,
        if (map != null)
            map.set(this, value);
        else
        //为空,再创建Map
            createMap(t, value);
        return value;
    }
  • createMap(t, value)的实现
    /**
     * 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);
    }

创建ThreadLocalMap对象并赋值给Thread中的threadLocals

经过这一系列流程,ThreadLocal是为每个线程创建变量的副本就很清晰:

  • 首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
  • 初始化Thread时,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
  • 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

总结

1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦 3.通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中 4.为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量 5.在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。 因为get方法中,getMap()默认为null,则返回setInitialValue(),setInitialValue()方法中, T value = initialValue() 默认返回null,最终会报空指针异常


参考

www.cnblogs.com/dolphin0520…