ThreadLocal学习笔记
1. 感觉自己在多线程方面知识很欠缺,平时做WEB开发,体会不是很深,因为Servlet容器、各种WEB框架已经支持了多线程,所以平时很少或者根本不需要考虑到多线程的问题,但最近在学习struts2等框架时,发现这些框架在好多地方都用到了ThreadLocal在处理多线程问题(其实我感觉就是变量传递问题),例如:Struts2中的ActionContext(将变量方便的在page和action中传递),Hibenate中的ThreadLocalSessionContext(将session跟当前线程关联),于是在网上找了些资料,想弄清楚这个ThreadLocal到底有什么神奇的地方,于是乎有了下面这些学习笔记。
2. 先来看看JDK文档中关于ThreadLocal的解释:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
它主要提供了以下2个方法,供调用:
(1) T get() 返回此线程局部变量的当前线程副本中的值
(2) void set(T t) 将此线程局部变量的当前线程副本中的值设置为指定值
按照我的理解,这个ThreadLocal主要处理多线程中变量的传递,比方说,我们的一个客户请求处理逻辑中,需要跨越很多个类的很多个方法,这些方法都需要使用同一个变量(如map,list),
方案1,通常我们的做法是,将这个变量map/list做为方法的参数向下一直传递下去,因为方法的参数相当于方法的局部变量,而局部变量是不存在多线程共同操作的问题;
方案2,可以将map/list放到request中,然后将request做为方法参数向下传递,
显然方案1、方案2存在很大的相似性,而且方案2显然不利于将来的测试,方案1,方案2在方法调用时都需要将变量作为方法参数传递,显然这样的设计不够优美。
于是,出现了ThreadLocal
方案3,ThreadLocal可以让我们的变量,做为线程的局部变量,这样在多线程中,不同线程之间的list与list是互不影响的,只需要从ThreadLocal调用get()就可以得到与当前线程关联的list,只要调用set()就可以让list与当前的线程关联,而不需要将list作为方法的参数一直向下传递,
3. 好了,我们来模拟下servlet中使用ThreadLocal来处理变量的传递问题,我们的类列表如下:
MyContext.java 这个就是变量的容器,我们只需要从这里得到变量,而不需要将变量作为参数传递
MyThread.java 线程,在run开始时,将变量跟线程绑定
MyServlet.java 模拟Servlet处理业务逻辑,修改我们的变量
MyPage.java 模拟页面,将变量的值打印出来
Test.java 模拟Servlet容器,用于启动多个线程
public class MyContext { // 将List<String>的一个对象,作为线程的局部变量 private static ThreadLocal<List<String>> mycontext = new ThreadLocal<List<String>>(); private MyContext(){} /** * 将ctx跟当前线程绑定 */ public static void set(List<String> ctx) { mycontext.set(ctx); } /** * 返回与当前线程相关的context */ public static List<String> get() { return mycontext.get(); } /** * 直接将str添加到与当前线程相关的context中 */ public static void add(String str) { if(str == null) return; List<String> list = mycontext.get(); if(list == null) { list = new ArrayList<String>(); mycontext.set(list); } list.add(str); } } |
public class MyThread extends Thread { public MyThread(String threadName) { super(threadName); } public void run() { // 创建对象 List<String> mycontext = new ArrayList<String>(); // 将对象与当前线程绑定 MyContext.set(mycontext); // 进入另外的调用方法,在其它方法方法里面,通过调用MyContext.get()可以得到与当前线程绑定的MyContext对象 new MyServlet().execute(); new MyPage().execute(); } } |
public class MyServlet { /** * 这个方法工作在多线程中 */ public void execute() { // 得到与当前线程相关联的MyContext对象 List<String> myContext = MyContext.get(); for(int i = 0; i < 3; i++) { sleep(10); myContext.add(Thread.currentThread().getName() + " - " + new Date().getTime()); } } private void sleep(int i) { try{Thread.sleep(i);}catch (InterruptedException e){e.printStackTrace();} } } |
public class MyPage { /** *模拟页面输出 */ public void execute() { // 得到与当前线程相关联的MyContext对象 List<String> myContext = MyContext.get(); // 执行完后,观察结果 for(String str : myContext) System.out.println(str); } } |
public class Test { public static void main(String[] args) throws InterruptedException { test2(); } /** * 模拟Servlet容器,启动3个线程 */ private static void test2() throws InterruptedException { MyThread t1 = new MyThread("线程1"); MyThread t2 = new MyThread("线程2"); MyThread t3 = new MyThread("线程3"); t1.start(); Thread.sleep(15); t2.start(); Thread.sleep(15); t3.start(); } } |
运行Test.java,输出结果:
线程1 - 1262862012671
线程1 - 1262862012687
线程1 - 1262862012703
线程2 - 1262862012687
线程2 - 1262862012703
线程2 - 1262862012718
线程3 - 1262862012703
线程3 - 1262862012718
线程3 – 1262862012734
我们可以看到,线程之间变量是没有影响的。
4.