Java并发编程-ThreadLocal

一、ThreadLocal 的理解

ThreadLocal 由 JDK 包提供,它提供了线程本地变量,如果创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量的时候,实际上是操作自己本地内存里面的变量,从而避免了线程安全问题。创建了一个 ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存中。如图所示:

ThreadLocal01.png

二、ThreadLocal 的示例

代码如下:

public class ThreadLocalTest {
	
	// 创建ThreadLocal变量
	public static ThreadLocal<String> localVariable = new ThreadLocal<>();
	
	// 打印方法
	public static void print(String str) {
		// 打印当前线程本地内存中localVariable变量的值
		System.out.println(str + ":" + localVariable.get());
		// 清除当前线程本地内存中localVariable变量的值
		localVariable.remove();
	}
	
	public static void main(String[] args) {
		
		// 创建线程thread1
		Thread thread1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				// 设置线程thread1中本地变量localVariable的值
				localVariable.set("thread1 local variable");
				print("thread1");
				System.out.println("thread1 remove after" + ":" + localVariable.get());	
			}
		});
		
		// 创建线程thread2
		Thread thread2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				// 设置线程thread2中本地变量localVariable的值
				localVariable.set("thread2 local variable");
				print("thread2");
				System.out.println("thread2 remove after" + ":" + localVariable.get());
			}
		});
		
		// 启动线程
		thread1.start();
		thread2.start();
		
	}

}

运行结果如下:

thread1:thread1 local variable
thread2:thread2 local variable
thread1 remove after:null
thread2 remove after:null

将 print() 方法中的 localVariable.remove() 注释,再次运行得到运行结果:

thread1:thread1 local variable
thread2:thread2 local variable
thread1 remove after:thread1 local variable
thread2 remove after:thread2 local variable

三、ThreadLocal 的原理

ThreadLocal 类提供的几种方法:

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

1、public void set(T value) { }

public void set(T value) {
    // (1)获取当前线程
    Thread t = Thread.currentThread();
    // (2)将当前线程作为key,查找对应的线程变量,如果找到则设置
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        // (3)如果第一次调用则创建当前线程对应的HashMap
        createMap(t, value);
}

代码(1)首先获取当前的使用线程,然后使用其作为参数传入 getMap(t) 方法,getMap(Thread t) 的代码如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可以看出,getMap(t) 的目的是获取当前线程自己的 threadLocals 变量。

如果 getMap(t) 的返回值不为空,则把 value 值设置到 threadLocals 中,也就是把当前变量值放入当前线程的内存变量 threadLocals 中。其中 this 代表 ThreadLocal 的实例对象引用,value 是通过 set 方法传递的值。

如果 getMap(t) 的返回值为空,则说明是第一次调用 set 方法,这时候会创建当前线程的 threadLocals 变量。createMap(t, value):

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看出,createMap(t, value) 方法创建当前线程的 threadLocals 变量。

2、T get() { }

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的threadLocals变量
    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();
}

首先获取当前线程实例,如果当前线程的 threadLocals 变量不为 null,则直接返回当前线程绑定的本地变量,否则进行初始化。

3、void remove() { }

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

如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量。

总结:每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中,如果当前线程一直不消亡,则这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后需要调用 ThreadLocal 的 remove 方法删除对应线程的 threadLocals 中的本地变量。如图所示:

ThreadLocal-zongjie.png

posted @ 2018-11-11 14:42  扇影无风  阅读(146)  评论(0编辑  收藏  举报