ThreadLocal理解和应用

谁说我不能喝 提交于 2020-03-01 07:11:13

本篇博客将为大家介绍一下ThreadLocal。从用途、使用方法、原理、及常见问题四个方面来介绍。

1.ThreadLocal用途

ThreadLocal用途可以理解成一个“储物间”,这个“储物间”当中拥有大量的“储物柜”,每个“储物柜”实际上就是每个线程,当中存放的是Thread线程中参数,针对于ThreadLocal的set方法其实就是将参数放入到当前线程对应的“储物柜”当中(根据Thread.currentThread()进行区分线程),同样get()和remove()方法也是一样。

2.ThreadLocal使用方法

下面我们编写一个程序,每个线程保存一个自己的ID数据,然后多次调用进行修改ID数据并将其打印。

public class ThreadLocalIds {

	private static final ThreadLocal<AtomicInteger> THREAD_LOCAL = new ThreadLocal<AtomicInteger>() {
		protected AtomicInteger initialValue() {
			return new AtomicInteger(0);// threadlocal中初始化值,每个线程的ID都从0开始增加
		};
	};

	public static int getIntValue() {
		AtomicInteger atomicInteger=THREAD_LOCAL.get();
		return atomicInteger.getAndIncrement();// 获取并增加
	}

	public static void remove() {
		THREAD_LOCAL.remove();// 移除
	}

}
public class ThreadLocalTest {
	public static void main(String[] args) {
		increment();//主线程
		new Thread(new Runnable() {//依次启动并创建三个线程

			@Override
			public void run() {
				increment();
			}
		}).start();
		new Thread(new Runnable() {

			@Override
			public void run() {
				increment();
			}
		}).start();
		new Thread(new Runnable() {

			@Override
			public void run() {
				increment();
			}
		}).start();
	}

	private static void increment() {
		try {
			for (int i = 0; i < 6; i++) {// 每个线程增加6次
				System.out.println("当前线程名:" + Thread.currentThread().getName() + " ,No." + i + ", intValue="
						+ ThreadLocalIds.getIntValue());
			}
		} finally {
			ThreadLocalIds.remove();
		}

	}
}

最后代码的执行结果如下,可以看到每个线程的ID都是依次从0开始增加。

当前线程名:main ,No.0, intValue=0
当前线程名:main ,No.1, intValue=1
当前线程名:main ,No.2, intValue=2
当前线程名:main ,No.3, intValue=3
当前线程名:main ,No.4, intValue=4
当前线程名:main ,No.5, intValue=5
当前线程名:Thread-0 ,No.0, intValue=0
当前线程名:Thread-0 ,No.1, intValue=1
当前线程名:Thread-0 ,No.2, intValue=2
当前线程名:Thread-0 ,No.3, intValue=3
当前线程名:Thread-0 ,No.4, intValue=4
当前线程名:Thread-0 ,No.5, intValue=5
当前线程名:Thread-1 ,No.0, intValue=0
当前线程名:Thread-1 ,No.1, intValue=1
当前线程名:Thread-1 ,No.2, intValue=2
当前线程名:Thread-1 ,No.3, intValue=3
当前线程名:Thread-1 ,No.4, intValue=4
当前线程名:Thread-1 ,No.5, intValue=5
当前线程名:Thread-2 ,No.0, intValue=0
当前线程名:Thread-2 ,No.1, intValue=1
当前线程名:Thread-2 ,No.2, intValue=2
当前线程名:Thread-2 ,No.3, intValue=3
当前线程名:Thread-2 ,No.4, intValue=4
当前线程名:Thread-2 ,No.5, intValue=5

3.ThreadLocal原理

1.ThreadLocal类结构:

ThreadLocal拥有三个方法,set(),get(),remove()以及内部类ThreadLocalMap

2.ThreadLocal及Thread之间的关系

从上面这张图可以看到Thread中属性threadLocal是作为一个特殊的Map,它的key值就是我们ThreadLocal实例,而value值就是我们设置的值。

然后我们看一下ThreadLocal类的源码:

    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();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

第二行代码getMap就是当前的线程中获取我们设置的ID值。如果是第一次来调用get方法,会执行初始值的操作,也就是我们赋值的0.

4.ThreadLocal常见的使用问题

关于ThreadLocal使用不当造成的内存泄漏问题:

上面ThreadLocal的源码中有一行是从ThreadLocalMap.Entry进行获取,Entry类这个是弱引用弱引用的回收是在JVM的下一次GC回收之前

那么我们可以总结一下ThreadLocal出现内存泄漏的条件:

  1. ThreadLocal的引用设置为null,且后续没有任何操作。
  2. 线程池中使用,线程一直运行没有停止。
  3. 触发了Minor GC或者是Full GC(这一条是根据其他大牛的博客得来)

平常我们在使用ThreadLocal时可以尽量申明为private static final或者使用之后进行remove(),这两种方法都可以避免出现内存泄漏的问题。 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!