ThreadLocal深入研究

不久前我写过一篇关于ThreadLocal用法的文章,但最近项目上出现了Memory Leak,调查后发现可能与ThreadLocal的使用有关,在此对ThreadLocal的使用作一些补充。

在ThreadLocal内部,其实是通过一个Map(类似Map<Thread, Object>)来保存各个线程独立的变量的,但是这个map有一点特殊,它对线程的引用是弱引用WeakReference(如果一个对象只被弱引用相联,那么GC就可以回收这个对象),这说明当线程执行结果后,即使没有显式的调用ThreadLocal.remove方法,GC也可以回收该线程在ThreadLocal中存放的独立对象了。

我们先看一个简单的例子:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Obj> local = new ThreadLocal<Obj>();

		Thread t = new Thread() {
			public void run() {
				local.set(new Obj());
			}
		};
		t.start();

		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}
}

class Obj {
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println(this + " finalized.");
	}
}

线程t开始执行时创建了一个Obj对象,随后把该对象放入ThreadLocal中,之后线程t执行结束。之后便会输出[email protected] finalized,说明Obj对象被GC回收,这与我们上面的分析是一致的。

我们对程序稍作修改,再来看看:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Obj> local = new ThreadLocal<Obj>();
		ExecutorService exec = Executors.newFixedThreadPool(2);

		Thread t = new Thread() {
			public void run() {
				local.set(new Obj());
			}
		};
		exec.execute(t);

		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}
}

class Obj {
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println(this + "finalized.");
	}
}

与之前的例子不同的地方是这里使用了线程池来执行线程,这样当线程执行完后并没有被销毁,而是还给了线程池。正因为此ThreadLocal Map中为该线程保存的entry不会被GC回收,也就是说上面这个例子不会有任何输出,Obj对象会在Heap中一直存在。

可以想象下在一个web server环境下,为了提高对请求的响应,大部分web server(比如tomcat)都是预先创建一个线程池。当有请求到来时,就从线程池中取出一个线程来处理请求,之后再将线程放回线程池,也就是说这些线程至始至终都不会被销毁。那如果像上面的例子一样在Web环境下错误地使用了ThreadLocal会带来什么后果呢?

我们再看一个例子:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Object> local = new ThreadLocal<Object>();
		ExecutorService exec = Executors.newFixedThreadPool(2);

		Thread t = new Thread() {
			public void run() {
				local.set(App.createObj());
			}
		};
		exec.execute(t);

		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}

	public static Object createObj()  {
		try {
			CustomClassLoader cl =
					new CustomClassLoader(new URL("file:///Users/ouyang/Develop/eclipse/workspace/Test/bin/"));

			Class<?> clazz = cl.loadClass("App$Obj");
	        return clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public static class Obj {
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			System.out.println(this + "finalized.");
		}
	}
}

class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader(URL... urls) {
        super(urls, null);
    }

    @Override
    protected void finalize() {
        System.out.println("*** CustomClassLoader finalized!");
    }
}

这个例子在之前例子的基础上,修改了Obj对象的创建,这次我们使用一个自定义的ClassLoader来加载和创建Obj对象。同样的,这个例子不会有任何的输出,Obj对象不能被GC回收,从而导致加载他的CustomClassLoader对象不能被回收,更要命的是其它被CustomClassLoader加载的类啊、静态数据对象等等,都不能被GC回收,甚至是在undeploy应用的时候都不能被回收。只要web server不重启,每一次重新布暑应用都将加大这些无效类、静态数据所占用的空间。从而造成Permgen
Leak和Memory Leak。

所以,必须在线程执行结束前,调用ThreadLocal的remove方法显式的删除对独立对象的强引用。

时间: 2024-10-14 03:10:10

ThreadLocal深入研究的相关文章

Java 并发:深入理解 ThreadLocal

摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个

深入研究java.lang.ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副

深入研究JAVA ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量 (ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程

Java中的闪光点:ThreadLocal是线程Thead的局部变量,可替代同步机制的设计,值得学习和研究

线程局部变量ThreadLocal,是Java支持的一种线程安全机制,目的是解决多线程的并发问题. 具体来讲,就是多个线程访问该实例对象的变量时,该实例对象将其存储为键值对的形式,保证各个线程(键)分别对应一份该变量值(值),从而保证多线程变量值得安全访问. ThreadLocal与同步机制比较 同步机制:用锁机制保证同一时间只有一个线程访问变量(用时间换空间),变量是多线程共享的,设计时要缜密分析什么时候读写?什么时候锁定?什么时候释放? ThreadLocal:提供每个线程一个独立的变量副本

菜鸟之路——Java并发之ThreadLocal

一.什么是ThreadLocal ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal的目的是为了解决多线程访问资源时的共享问题.但其实这么说并不准确.ThreadLocal是为变量在每个线程中都创建了一个副本(此副本的意思是通过每个线程中的new操作来创建内容一样的新的对象,每个线程创建一个,而不是使用对象的引用),使每个线程可以访问自己内部的副

java并发:线程同步机制之ThreadLocal

1.简述ThreadLocal ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程.ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点: A.当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应

Android多线程研究(1)——线程基础及源代码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override p

Java_解密ThreadLocal

概述 相信读者在网上也看了很多关于ThreadLocal的资料,很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路:ThreadLocal的目的是为了解决多线程访问资源时的共享问题.如果你也这样认为的,那现在给你10秒钟,清空之前对ThreadLocal的错误的认知! 看看JDK中的源码是怎么写的: This class provides thread-local variables. These variables differ from their norm

ThreadLocal源码解析

ThreadLocal经常被使用到,但是一直没有研究过它的实现原理. 在看源码之前,我猜测它是这样设计的:ThreadLocal中包含一个Map<Thread,Map<ThreadLocal, V>>属性,在使用时,通过Thread.currentThread()获取到当前线程,从而根据Key,找到Map<ThreadLocal, V>,后来发现是错的.所以本文通过阅读JDK1.7源码,来解析ThreadLocal的设计思想和原理. ThreadLocal只包含一个属性