一 ThreadLocal
(1) Threadlocal定义:
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
(2) ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
(3) ThreadLocal是如何实现的:
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本。
(4)ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
(5)ThreadLocal的使用场景(这个很重要)
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了线程保持对象的方法和避免参数传递的方便的对象访问方式,ThreadLocal的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
我说明一种使用ThreadLocal的情况。设置你使用了Servlet调用一些业务方法。你有个需求即:为记录日志每一个请求该servlet的过程生成一个唯一的业务标识并把它传递到业务方法中。
一个解决方法是把这个业务标识作为一个参数传递给每一个方法。但这不是不念旧恶好的解决方案因为代码是冗余且没有必要的。
要解决它,就要用到ThreadLocal,你生成一个业务标识并把它设置到ThreadLocal中,在这之后,任何一个业务方法,这个servlet调能从这个变量中访问业务标识。
这个Servlet可能服务于不止一个请求。因为每个请求都是一个线程,因此业务标识对每个线程是唯一的(局部),且对线程的整个执行过程是可见的(全局)
(6) ThreadLocal 例子
Conneciton代表一个连接,一个线程内,都用一个; 不同的线程用不同的connection 实例;
public class Connection { private int conId; public Connection(int id) { // TODO Auto-generated constructor stub } public int getConId() { return conId; } public void setConId(int conId) { this.conId = conId; } }
TestClass的实例只要一个,但是 con 是每个线程有一个副本
import java.util.Random; public class TestClass implements Runnable { private ThreadLocal<Connection> con = new ThreadLocal<Connection>() { public Connection initialValue(){ return new Connection(0); } }; public static void main(String[] args) { TestClass t1 = new TestClass(); new Thread(t1,"t1").start(); new Thread(t1,"t2").start(); } public void run() { Random i = new Random(); int value = i.nextInt(100); System.out.println("before set, current thread value " + Thread.currentThread() + ":" + value); con.set(new Connection(value)); System.out.println("after set, current thread value " + Thread.currentThread() + ":" + value); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Finally current thread value " + Thread.currentThread() + ":" + value); } }
输出结果:
before set, current thread value Thread[t1,5,main]:71
after set, current thread value Thread[t2,5,main]:12
after set, current thread value Thread[t1,5,main]:71
Finally current thread value Thread[t2,5,main]:12
Finally current thread value Thread[t1,5,main]:71