标签:思想 冲突 main swa ges 生成 详细 mes runnable
(转) http://www.cnblogs.com/moongeek/p/7857794.html
ThreadLocal,即线程本地变量。它可以将变量绑定到特定的线程上的入口,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不影响,是实现共享资源的轻量级同步。
下面是个ThreadLocal使用的实例,两个任务共享同一个变量,并且两个任务都把该变量设置为了线程私有变量,这样,虽然两个任务都”持有“同一变量,但各自持有该变量的拷贝。因此,当一个线程修改该变量时,不会影响另一线程该变量的值。
1 public class LocalTest1 implements Runnable {
2 // 一般会把 ThreadLocal 设置为static 。它只是个为线程设置局部变量的入口,多个线程只需要一个入口
3 private static ThreadLocal<Student> localStudent = new ThreadLocal() {
4 // 一般会重写初始化方法,一会分析源码时候会解释为什么
5 @Override
6 public Student initialValue() {
7 return new Student();
8 }
9 };
10
11 private Student student = null;
12
13 @Override
14 public void run() {
15 String threadName = Thread.currentThread().getName();
16
17 System.out.println("【" + threadName + "】:is running !");
18
19 Random ramdom = new Random();
20 //随机生成一个变量
21 int age = ramdom.nextInt(100);
22
23 System.out.println("【" + threadName + "】:set age to :" + age);
24 // 获得线程局部变量,改变属性值
25 Student stu = getStudent();
26 stu.setAge(age);
27
28 System.out.println("【" + threadName + "】:第一次读到的age值为 :" + stu.getAge());
29
30 try {
31 TimeUnit.SECONDS.sleep(2);
32 } catch (InterruptedException e) {
33 e.printStackTrace();
34 }
35
36 System.out.println("【" + threadName + "】:第二次读到的age值为 :" + stu.getAge());
37 }
38
39 public Student getStudent() {
40 student = localStudent.get();
41
42 // 如果不重写初始化方法,则需要判断是否为空,然后手动为ThreadLocal赋值
43 // if(student == null){
44 // student = new Student();
45 // localStudent.set(student);
46 // }
47
48 return student;
49 }
50
51 public static void main(String[] args) {
52 LocalTest1 ll = new LocalTest1();
53 Thread t1 = new Thread(ll, "线程1");
54 Thread t2 = new Thread(ll, "线程2");
55
56 t1.start();
57 t2.start();
58 }
59 }
60
61 public class Student{
62 private int age;
63
64 public Student(){
65
66 }
67 public Student(int age){
68 this.age = age;
69 }
70
71 public int getAge() {
72 return age;
73 }
74
75 public void setAge(int age) {
76 this.age = age;
77 }
78 }
运行结果:
【线程1】:is running ! 【线程2】:is running ! 【线程2】:set age to :45 【线程1】:set age to :25 【线程1】:第一次读到的age值为 :25 【线程2】:第一次读到的age值为 :45 【线程1】:第二次读到的age值为 :25 【线程2】:第二次读到的age值为 :45
public ThreadLocal{
public T get() {}
public void set(T value) {}
public void remove() {}
}
1 public void set(T value) {
2 // 获得当前线程
3 Thread t = Thread.currentThread();
4 // 获得当前线程的 ThreadLocalMap 引用,详细见下
5 ThreadLocalMap map = getMap(t);
6 // 如果不为空,则更新局部变量的值
7 if (map != null)
8 map.set(this, value);
9 //如果不是第一次使用,先进行初始化
10 else
11 createMap(t, value);
12 }
1 ThreadLocalMap getMap(Thread t) {
2 //返回该线程Thread的成员变量threadLocals
3 return t.threadLocals;
4 }
但是,Thread 默认把threadLocals设置为了null,因此第一次使用局部变量时候需要先初始化。
ThreadLocal.ThreadLocalMap threadLocals = null;
1 public T get() {
2 //获得当前线程
3 Thread t = Thread.currentThread();
4 //得到当前线程的一个threadLocals 变量
5 ThreadLocalMap map = getMap(t);
6 if (map != null) {
7 // 如果不为空,以当前ThreadLocal为主键获得对应的Entry
8 ThreadLocalMap.Entry e = map.getEntry(this);
9 if (e != null) {
10 @SuppressWarnings("unchecked")
11 T result = (T)e.value;
12 return result;
13 }
14 }
15 //如果值为空,则进行初始化
16 return setInitialValue();
17 }
1 private T setInitialValue() {
2 //获得初始默认值
3 T value = initialValue();
4 //得到当前线程
5 Thread t = Thread.currentThread();
6 // 获得该线程的ThreadLocalMap引用
7 ThreadLocalMap map = getMap(t);
8 //不为空则覆盖
9 if (map != null)
10 map.set(this, value);
11 else
12 //若是为空,则进行初始化,键为本ThreadLocal变量,值为默认值
13 createMap(t, value);
14 }
15
16 // 默认初始化返回null值,这也是为什么需要重写该方法的原因。如果没有重写,第一次get()操作获得的线程本地变量为null,需要进行判断并手动调用set()进行初始化
17 protected T initialValue() {
18 return null;
19 }
// table 默认大小,大小为2的次方,用于hash定位 private static final int INITIAL_CAPACITY = 16; // 存放键值对的数组 private Entry[] table; // 扩容的临界值,当table元素大到这个值,会进行扩容 private int threshold;
1 private void set(ThreadLocal<?> key, Object value) {
2 Entry[] tab = table;
3 int len = tab.length;
4 // Hash 寻址,与table数组长度减1(二进制全是1)相与,所以数组长度必须为2的次方,减小hash重复的可能性
5 int i = key.threadLocalHashCode & (len-1);
6
7 //从hash值计算出的下标开始遍历
8 for (Entry e = tab[i];
9 e != null;
10 e = tab[i = nextIndex(i, len)]) {
11 //获得该Entry的键
12 ThreadLocal<?> k = e.get();
13 //如果键和传过来的相同,覆盖原值,也说明,一个ThreadLocal变量只能为一个线程保存一个局部变量
14 if (k == key) {
15 e.value = value;
16 return;
17 }
18 // 键为空,则替换该节点
19 if (k == null) {
20 replaceStaleEntry(key, value, i);
21 return;
22 }
23 }
24
25 tab[i] = new Entry(key, value);
26 int sz = ++size;
27 //是否需要扩容
28 if (!cleanSomeSlots(i, sz) && sz >= threshold)
29 rehash();
30 }
为什么说数组长度为2的次方有利于hash计算不重复呢?我们来看下,显然,和一个二进制全是1的数相于,能最大限度的保证原数的所有位数,因而重复几率会变小。
1 private Entry getEntry(ThreadLocal<?> key) {
2 //Hash计算数组下标
3 int i = key.threadLocalHashCode & (table.length - 1);
4 //得到该下标的节点
5 Entry e = table[i];
6 //如果该节点存在,并且键和传过来的ThreadLocal对象相同,则返回该节点(说明该节点没有进行Hash冲突处理)
7 if (e != null && e.get() == key)
8 return e;
9 //如果该节点不直接满足需求,可能进行了Hash冲突处理,则另外处理
10 else
11 return getEntryAfterMiss(key, i, e);
12 }
1 // if (e == null || e.get() != key)
2 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
3 Entry[] tab = table;
4 int len = tab.length;
5 //从洗标为i开始遍历,直到遇到下一空节点或或是满足需求的节点
6 while (e != null) {
7 ThreadLocal<?> k = e.get();
8 if (k == key)
9 return e;
10 if (k == null)
11 //节点不为空,键为空,则清理该节点
12 expungeStaleEntry(i);
13 else
14 // i后移
15 i = nextIndex(i, len);
16 e = tab[i];
17 }
18 //否则返回空值
19 return null;
20 }
综上所述可知,ThreadLocal 只是访问Thread本地变量的一个入口,正真存储本地变量的其实是在Thread本地,同时ThreadLocal也作为一个键去Hash找到变量所在的位置。也许你会想,为什么不把ThreadLocalMap设置为< Thread,Variable>类型,把Thread作为主键,而要增加一个中间模块ThreadLocal?我的想法是,一来,这样确实可以满足需求,但是这样无法进行hash查找,如果一个Thread的本地变量过多,通过线性查找会花费大量时间,使用ThreadLocal作为中间键,可以进行Hash查找;二来,其实本地变量的添加、查找和删除需要进行大量的操作,设计者的思路是把这些操作封装在一个ThreadLocal类里,而只暴露了三个常用的接口,如果把ThreadLocal去掉,这些操作可能要写在Thread类里,违背了设计类的“单一性”原则;三来,我们这样相当于为每个本地变量取了个“名字”(即,一个ThreadLocal对应一个本地变量),使用方便。
标签:思想 冲突 main swa ges 生成 详细 mes runnable
原文地址:http://www.cnblogs.com/latteyan/p/7879643.html