`

正确理解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中都能得到这个实例,而不需要把这个实例作为参数从高层模块传递到低层模块中去。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics