`

正确理解ThreadLocal

    博客分类:
  • Java
阅读更多

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程(当前线程)自己使用的对象,其他线程是不能访问得到的,各个线程中访问的是不同的对象。另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每创建一个新的线程就会有一个ThreadLocal.ThreadLocalMap threadLocals,每个线程在适当的时候(在当前线程中第一次访问ThreadLocal的get或set方式时)创建一个这样的threadLocals变量,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个ThreadLocalMap变量中,每个线程都有这样一个ThreadLocal.ThreadLocalMap threadLocals,执行ThreadLocal.get()时,先会取出当前线程所对应的ThreadLocalMap,再以ThreadLocal实例为Key从当前线程自己的ThreadLocalMap中取出放进去的对象,因此取出来的是各自自己线程中的对象。如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。下面来看看源码:
Thread类中有一个ThreadLocalMap变量,开始是null:

ThreadLocal.ThreadLocalMap threadLocals = null;
 

由第一次访问ThreadLocal的get或set方法时设置,下面是ThreadLocal中的get与set方法(getMap方法直接使用代码替换过了):

    public Object get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;//取当前线程中的ThreadLocalMap
        if (map != null)
            return map.get(this);//从当前线程的ThreadLocalMap变量中取出key为当前ThreadLocal实例的线程局部变量

        // Maps are constructed lazily.  if the map for this thread
        // doesn't exist, create it, with this ThreadLocal and its
        // initial value as its only entry.
        Object value = initialValue();
        createMap(t, value);
        return value;
}

    public void set(Object value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null)
            map.set(this, value);//以当前ThreadLocal实例为key,存储线程局部变量到ThreadLocalMap中
        else
            createMap(t, value);
}

 

上面的get与set方法在map为null(第一次访问时,即当前线程中所有的ThreadLocal变量中的第一个第一次访问,只要访问过了,其他的ThreadLocal变量再次访问不会再调用createMap)会调用createMap来为线程Thread类的threadLocals变量赋值,createMap源码如下:

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

 

从上面的代码可以看出,每个线程会有一个ThreadLocalMap变量,而ThreadLocalMap中的key为当前ThreadLocal实例。

总之,ThreadLocal 不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1、每个线程中都有一个自己的ThreadLocalMap 类对象,可以将当前线程中某些对象保持到其中,各管各的,线程在适当时候可以正确的访问到这些对象。
2、将一个共用的ThreadLocal 静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中(但key都是同一个,即ThreadLocal 静态实例),然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
比如这里要写一个实例,每个线程的日志要记录到各自的日志文件中去,这样避免了多个线程的日志记录到同一文件中时可能出现日志打印顺序混乱,这就要求以线程为范围及,每个线程一个日志文件对象,并且在整个线程中的方法调用栈中使用同一个:

import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Logger {
	private PrintWriter writer = null;

	// 初始化writer
	public Logger(String filename) {
		try {
			writer = new PrintWriter(new FileWriter(filename));
			writer.println("==== Start of log ====");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// 日志写入文件
	public void println(String s) {
		writer.println(s);
	}

	// 关闭文件
	public void close() {
		writer.println("==== End of log ====");
		writer.close();
	}
}
 
/**
 * 日志记录器,供所有的线程调用,但每个线程却有独立的Logger
 * 
 */
public class Log {
	// 线程局部变量,可以为每个线程创建一个仅且一个的日志文件(其实就是使logger对象线程局部化)
	private static final ThreadLocal loggerThreadLocal = new ThreadLocal();

	// 记录日志
	public static void println(String s) {
		getLogger().println(s);
	}

	// 关闭日志
	public static void close() {
		getLogger().close();
	}

	/**
	 * 取得调用Log类的当前线程所持有的Logger日志记录器
	 * 这样确保每个线程所记录的日志记录到同一文件中
	 * @return
	 */
	private static Logger getLogger() {
		Logger logger = (Logger) loggerThreadLocal.get();

		// 如果线程是第一次呼叫,就建立新挡案并登陆log
		if (logger == null) {
			logger = new Logger(Thread.currentThread().getName() + "-log.txt");

			// 为每个线程提供唯一一个logger对象
			loggerThreadLocal.set(logger);
		}

		return logger;
	}
}
 
public class Service1 {

	public void method1() {
		Log.println(Thread.currentThread().getName() + " - Service1.method1()");
		new Service2().method2();
	}
}


public class Service2 {

	public void method2() {
		Log.println(Thread.currentThread().getName() + " - Service2.method2()");
	}
}
 
public class ClientThread extends Thread {
	public static void main(String[] args) {
		new ClientThread("A").start();
		new ClientThread("B").start();
		new ClientThread("C").start();
	}

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

	public void run() {
		System.out.println(getName() + " BEGIN");
		for (int i = 0; i < 10; i++) {
			/*
			 * 每个线程在第一次记录日志时,都会先创建一个新的 Logger ,以后记录
			 * 会使用第一次创建的 Logger
			 */
			Log.println("i = " + i);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}
		//关闭时也只是关闭本线程的文件,对其他线程不影响
		Log.close();
		System.out.println(getName() + " END");
	}
}
 


在这个实例中我们启动了3个线程,每个线程都会调用Serivce1的method1方法,再由Service1的method1方法调用Service2的method2方法,这些调用的过程都会记录日志,且日志记录到同一线程日志文件里,且互不干扰。打开的文件流最后在线程运行结束前关闭,这样确保了输出文件流在整个线程中是可用的。所以我们这里的Logger是一个线程局部化的类,每个线程都会需要且只需一这样一个类的实例。在该实例里,我们在每个线程启动时就创建了一个Logger实例,且存储到ThreadLocalMap中,所以我们在Service1与Service2中都能得到这个实例,而不需要把这个实例作为参数从高层模块传递到低层模块中去。

分享到:
评论

相关推荐

    正确理解ThreadLocal.pdf

    ### 正确理解ThreadLocal:深入解析其工作原理与应用场景 #### 一、ThreadLocal的基本概念 `ThreadLocal`是Java平台提供的一种线程局部变量的解决方案,它为每一个使用该变量的线程都提供了独立的变量副本,使得每...

    ThreadLocal

    - 内存泄漏:如果线程长时间存活,或者ThreadLocal对象没有被正确清理,可能导致ThreadLocalMap中的引用无法被垃圾回收,从而造成内存泄漏。 - 不适用于跨线程通信:ThreadLocal只保证同一线程内的数据隔离,不同...

    threadLocal

    1. 多线程:理解ThreadLocal的使用必须建立在对多线程的理解基础上,包括线程的创建、执行、同步机制等。 2. 并发编程:ThreadLocal是解决并发问题的一种策略,它提供了一种避免共享状态的方式,减少了锁的使用。 3....

    ThreadLocal的几种误区

    ThreadLocal是Java编程中一种非常特殊的变量类型,它主要...正确使用ThreadLocal可以提高代码的并发性能,而滥用则可能导致难以预料的问题。因此,在使用ThreadLocal时,需要充分考虑其生命周期管理和线程安全问题。

    ThreadLocal 内存泄露的实例分析1

    at 中专门为每一个 web 应用...理解 `ThreadLocal` 的工作原理以及它如何与类加载器交互,是避免此类问题的关键。在实际开发中,应当养成良好的编程习惯,如使用后及时清理 `ThreadLocal` 变量,以防止内存资源的浪费。

    设计模式及ThreadLocal资料

    本资料主要聚焦于两种设计模式以及Java中的ThreadLocal特性。 首先,我们来探讨单例模式。单例模式是一种确保一个类只有一个实例,并提供全局访问点的设计模式。在Java中,通常通过私有构造函数、静态工厂方法或...

    深入理解 Java 之 ThreadLocal 工作原理1

    需要注意的是,尽管ThreadLocal提供了线程局部的变量,但如果不正确地使用,比如在ThreadLocal生命周期结束后没有清理ThreadLocalMap,可能会导致内存泄漏。因此,最佳实践是在不再需要ThreadLocal时调用remove方法...

    Spring事务处理-ThreadLocal的使用

    在实际应用中,理解ThreadLocal在Spring事务处理中的作用有助于优化并发性能和解决多线程环境下的事务问题。例如,如果线程之间需要共享数据,但又不想影响其他线程,ThreadLocal就是一个理想的选择。同时,也要注意...

    java中ThreadLocal类的使用

    - **内存泄漏**:如果`ThreadLocal`没有被正确清理,当线程生命周期结束但引用仍然存在时,可能导致内存泄漏。因此,通常在不再需要`ThreadLocal`时,应该调用`remove`方法。 - **性能影响**:过度使用`ThreadLocal`...

    Java资料-详解ThreadLocal

    让我们通过一个简单的例子来理解`ThreadLocal`的工作机制: ```java public class ThreadLocalExample { private static ThreadLocal&lt;Integer&gt; threadLocal = new ThreadLocal(); public static void main...

    ThreadLocal的原理,源码深度分析及使用.docx

    ThreadLocal 翻译过来就是本地线程,但是直接这么翻译很难理解 ThreadLocal 的作用。如果换一种说法,可以称为线程本地存储。简单来说,就是 ThreadLocal 为共享变量在每个线程中都创建一个副本,每个线程可以访问...

    简单分析Java线程编程中ThreadLocal类的使用共

    理解ThreadLocal的工作原理以及如何正确使用它是Java并发编程的关键。 首先,ThreadLocal类的主要作用是为每个线程创建一个单独的变量副本。这意味着在同一个ThreadLocal变量中,不同的线程可以存储各自独立的值,...

    Python之ThreadLocal共4页.pdf.zip

    Python中的ThreadLocal,全称是`threading.local`,是一个非常重要的并发编程工具。它主要用于在多线程环境中保持线程局部变量,确保每个...正确理解和使用ThreadLocal能够帮助开发者编写出更加健壮和高效的并发程序。

    java并发包源码分析(3)ThreadLocal

    测试用例可以帮助我们更好地理解ThreadLocal的使用和潜在问题: - 创建多个线程,每个线程使用ThreadLocal变量,如果变量引用的是同一个共享对象,修改其中一个线程的变量值,其他线程是否也会看到改变,以此来观察...

    ThreadLocal 线程本地变量 及 源码分析.rar_开发_设计

    ThreadLocal是Java编程语言中的一个类,用于在多线程环境下提供线程局部变量。它为每个线程创建了一个独立的...通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。

    java ThreadLocal使用案例详解

    在本文中,我们将详细介绍Java ThreadLocal的使用案例,并通过一个实际的优化案例,帮助大家理解ThreadLocal的使用。 ThreadLocal的使用场景 在Java多线程编程中,变量的线程安全是一个非常重要的问题。特别是在...

Global site tag (gtag.js) - Google Analytics