当前所在位置:珠峰网资料 >> 计算机 >> 计算机等级考试 >> 正文
Java线程:深入ThreadLocal
发布时间:2010/1/7 11:45:22 来源:www.xue.net 编辑:城市总裁吧

  ThreadLocal与线程成员变量还有区别,ThreadLocal该类提供了线程局部变量。这个局部变量与一般的成员变量不一样,ThreadLocal的变量在被多个线程使用时候,每个线程只能拿到该变量的一个副本,这是Java API中的描述,通过阅读API源码,发现并非副本,副本什么概念?克隆品? 或者是别的样子,太模糊。

  准确的说,应该是ThreadLocal类型的变量内部的注册表(Map<Thread,T>)发生了变化,但ThreadLocal类型的变量本身的确是一个,这才是本质!

  下面就做个例子:

  一、标准例子

  定义了MyThreadLocal类,创建它的一个对象tlt,分别给四个线程使用,结果四个线程tlt变量并没有出现共用现象,二是各用各的,这说明,四个线程使用的是tlt的副本(克隆品)。

  /**

  * 使用了ThreadLocal的类

  *

  */

  public class MyThreadLocal {

  //定义了一个ThreadLocal变量,用来保存int或Integer数据

  private ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {

  @Override

  protected Integer initialValue() {

  return 0;

  }

  };

  public Integer getNextNum() {

  //将tl的值获取后加1,并更新设置t1的值

  tl.set(tl.get() + 1);

  return tl.get();

  }

  }

  /**

  * 测试线程

  *

  */

  public class TestThread extends Thread {

  private MyThreadLocal tlt = new MyThreadLocal();

  public TestThread(MyThreadLocal tlt) {

  this.tlt = tlt;

  }

  @Override

  public void run() {

  for (int i = 0; i < 3; i++) {

  System.out.println(Thread.currentThread().getName() + "\t" + tlt.getNextNum());

  }

  }

  }

  /**

  * ThreadLocal测试

  *

  */

  public class Test {

  public static void main(String[] args) {

  MyThreadLocal tlt = new MyThreadLocal();

  Thread t1 = new TestThread(tlt);

  Thread t2 = new TestThread(tlt);

  Thread t3 = new TestThread(tlt);

  Thread t4 = new TestThread(tlt);

  t1.start();

  t2.start();

  t3.start();

  t4.start();

  }

  }

  可以看出,三个线程各自独立编号,互不影响:

  Thread-0  1

  Thread-1  1

  Thread-0  2

  Thread-1  2

  Thread-0  3

  Thread-1  3

  Thread-2  1

  Thread-3  1

  Thread-2  2

  Thread-3  2

  Thread-2  3

  Thread-3  3

  Process finished with exit code 0

  tlt对象是一个,废话tl对象也是一个,因为组合关系是一对一的。但是tl对象内部的Map随着线程的增多,会创建很多Integer对象。只是Integer和int已经通用了。所以感觉不到Integer的对象属性。[NextPage]

二、不用ThreadLocal

  假如不用ThreadLocal,只需要将MyThreadLocal类重新定义为:

  /**

  * 使用了ThreadLocal的类

  *

  */

  public class MyThreadLocal {

  private Integer t1 = 0;

  public Integer getNextNum(){

  return t1=t1+1;

  }

  //        //定义了一个ThreadLocal变量,用来保存int或Integer数据

  //        private ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {

  //                @Override

  //                protected Integer initialValue() {

  //                        return 0;

  //                }

  //        };

  //

  //        public Integer getNextNum() {

  //                //将tl的值获取后加1,并更新设置t1的值

  //                tl.set(tl.get() + 1);

  //                return tl.get();

  //        }

  }

  然后运行测试:

  Thread-2  1

  Thread-2  2

  Thread-1  4

  Thread-1  6

  Thread-3  3

  Thread-3  9

  Thread-3  10

  Thread-1  8

  Thread-0  7

  Thread-0  11

  Thread-0  12

  Thread-2  5

  Process finished with exit code 0

  从这里可以看出,四个线程共享了tlt变量,结果每个线程都直接修改tlt的属性。[NextPage]

三、自己实现个ThreadLocal

  package com.lavasoft.test2;

  import java.util.Collections;

  import java.util.HashMap;

  import java.util.Map;

  /**

  * 使用了ThreadLocal的类

  *

  */

  public class MyThreadLocal {

  //定义了一个ThreadLocal变量,用来保存int或Integer数据

  private com.lavasoft.test2.ThreadLocal<Integer> tl = new com.lavasoft.test2.ThreadLocal<Integer>() {

  @Override

  protected Integer initialValue() {

  return 0;

  }

  };

  public Integer getNextNum() {

  //将tl的值获取后加1,并更新设置t1的值

  tl.set(tl.get() + 1);

  return tl.get();

  }

  }

  class ThreadLocal<T> {

  private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());

  public ThreadLocal() {

  }

  protected T initialValue() {

  return null;

  }

  public T get() {

  Thread t = Thread.currentThread();

  T obj = map.get(t);

  if (obj == null && !map.containsKey(t)) {

  obj = initialValue();

  map.put(t, obj);

  }

  return obj;

  }

  public void set(T value) {

  map.put(Thread.currentThread(), value);

  }

  public void remove() {

  map.remove(Thread.currentThread());

  }

  }

  运行测试:

  Thread-0  1

  Thread-0  2

  Thread-0  3

  Thread-2  1

  Thread-2  2

  Thread-3  1

  Thread-2  3

  Thread-3  2

  Thread-1  1

  Thread-3  3

  Thread-1  2

  Thread-1  3

  Process finished with exit code 0

  很意外,这个山寨版的ThreadLocal也同样运行很好,实现了JavaAPI中ThreadLocal的功能。[NextPage]
四、透过现象看本质   其实从程序角度看,tlt变量的确是一个,毫无疑问的。但是为什么打印出来的数字就互不影响呢?   是因为使用了Integer吗?-----不是。   原因是:protected T initialValue()和get(),因为每个线程在调用get()时候,发现Map中不存在就创建。调用它的时候,就创建了一个新变量,类型为T。每次都新建,当然各用个的互不影响了。   为了看清本质,将Integer换掉,重写部分类:   package com.lavasoft.test2;   import java.util.Collections;   import java.util.HashMap;   import java.util.Map;   /**   * 使用了ThreadLocal的类   *   */   public class MyThreadLocal {   //定义了一个ThreadLocal变量,用来保存int或Integer数据   //        private ThreadLocal tl = new ThreadLocal() {   private com.lavasoft.test2.ThreadLocal tl = new com.lavasoft.test2.ThreadLocal() {   @Override   protected Bean initialValue() {   return new Bean();   }   };   @Override   public String toString() {   return "MyThreadLocal{" +   "tl=" + tl +   '}';   }   public Bean getBean() {   return tl.get();   }   }   class ThreadLocal {   private Map map = Collections.synchronizedMap(new HashMap());   public ThreadLocal() {   }   protected T initialValue() {   return null;   }   public T get() {   Thread t = Thread.currentThread();   T obj = map.get(t);   if (obj == null && !map.containsKey(t)) {   obj = initialValue();   map.put(t, obj);   }   return obj;   }   public void set(T value) {   map.put(Thread.currentThread(), value);   }   public void remove() {   map.remove(Thread.currentThread());   }   }   package com.lavasoft.test2;   /**   * 测试Bean   *   */   public class Bean {   private String id = "0";   private String name = "none";   public Bean() {   }   public Bean(String id, String name) {   this.id = id;   this.name = name;   }   public String getId() {   return id;   }   public void setId(String id) {   this.id = id;   }   public String getName() {   return name;   }   public void setName(String name) {   this.name = name;   }   public String showinfo() {   return "Bean{" +   "id='" + id + '\'' +   ", name='" + name + '\'' +   '}';   }   }   package com.lavasoft.test2;   /**   * 测试线程   *   */   public class TestThread extends Thread {   private MyThreadLocal tlt = new MyThreadLocal();   public TestThread(MyThreadLocal tlt) {   this.tlt = tlt;   }   @Override   public void run() {   System.out.println(">>>>>:" + tlt);   for (int i = 0; i < 3; i++) {   System.out.println(Thread.currentThread().getName() + "\t" +tlt.getBean()+"\t"+tlt.getBean().showinfo());   }   }   }   然后运行测试:   >>>>>:MyThreadLocal{tl=com.lavasoft.test2.MyThreadLocal$1@1de3f2d}   >>>>>:MyThreadLocal{tl=com.lavasoft.test2.MyThreadLocal$1@1de3f2d}   >>>>>:MyThreadLocal{tl=com.lavasoft.test2.MyThreadLocal$1@1de3f2d}   >>>>>:MyThreadLocal{tl=com.lavasoft.test2.MyThreadLocal$1@1de3f2d}   Thread-1  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Thread-2  com.lavasoft.test2.Bean@fe64b9  Bean{id='0', name='none'}   Thread-3  com.lavasoft.test2.Bean@186db54  Bean{id='0', name='none'}   Thread-2  com.lavasoft.test2.Bean@fe64b9  Bean{id='0', name='none'}   Thread-2  com.lavasoft.test2.Bean@fe64b9  Bean{id='0', name='none'}   Thread-0  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Thread-3  com.lavasoft.test2.Bean@186db54  Bean{id='0', name='none'}   Thread-3  com.lavasoft.test2.Bean@186db54  Bean{id='0', name='none'}   Thread-1  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Thread-0  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Thread-0  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Thread-1  com.lavasoft.test2.Bean@291aff  Bean{id='0', name='none'}   Process finished with exit code 0   从打印结果很清楚的看到,MyThreadLocal的tlt对象的确是一个,tlt对象里的ThreadLocal的tl对象也是一个,但是,将t1t给每个线程用的时候,线程会重新创建Bean对象加入到ThreadLocal的Map中去使用。

广告合作:400-664-0084 全国热线:400-664-0084
Copyright 2010 - 2017 www.my8848.com 珠峰网 粤ICP备15066211号
珠峰网 版权所有 All Rights Reserved