ThreadLocal的理解

 很多人对ThreadLocal和Thread这两个之间关系不理解,先看Thread的源码,这样才可以了解到Thread和ThreadLocal的内在联系,查看源码发现每个线程都有ThreadLocal的内部类ThreadLocalMap类的变量  ,线程源码片段:

       public  class Thread implements Runnable {

                    ThreadLocal.ThreadLocalMap threadLocals = null;

                     .......

 

   }

 

查看ThreadLocal的set方法看做了那些操作

public class ThreadLocal<T> {

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

       .......

 

}

 

会发现1:找到当前线程对象,2该线程对象中取出ThreadLocalMap,如果当前线程有ThreadLocalMap,就把值放入该线程的ThreadLocalMap中,key是ThreadLocal,value是放入的值,如果没有则创建一个新的ThreadLocalMap对象,记住ThreadLocal通过set方法设置值时,是把该值存在当前线程的Thread的ThreadLocalMap中,与当前线程有关,到这里可能会有人不解,ThreadLocalMap是啥,是如何保存value值的,再次查看源码:

 

public class ThreadLocal<T> {

 

    static class ThreadLocalMap {

           static class Entry extends WeakReference<ThreadLocal> {
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }

   }

     private Entry[] table;

 

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

 

}

 

}

 

通过源码可以发现,ThreadLocalMap是ThreadLocal的一个静态内部类,该内部类里面有一个Entry类型的table的数组,通过ThreadLocal来得到该数组的下标值,然后在该位置上引用到new出来的Entry对象,简单的说就是Thread对象有一个ThreadLocalMap对象属性,该属性对象里面会有一个数组,这个数组存储的就是value值,当然是封装以后的value值。

 

public class ThreadLocal<T> {

 

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

 

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 

}

 

通过阅读上面代码,可以发现,ThreadLocal的get方法是找到当前线程,取得该线程对象的ThreadLocalMap属性对象,通过ThreadLocal作为key值而得到的value值,set方法亦如此。原来value值是存在每个线程对象的ThreadLocalMap属性中,ThreadLocal是作为该“map"的 key值,通过key就可以把每个线程对象存储的value值出来了。这样会发现如果在线程里面存储的是方法的局部变量的话,那么每个线程可以自由的改变value值,而对其他线程的value没影响,如果所有线程共用value对象,则线程改变value,同时也改变了其他线程的value值,

例子:

package com.threadlocal.test;

import itcast.io.Student;

import java.util.Random;

public class ThreadLocalTest2 implements Runnable{
 
 private ThreadLocal threadLocal=new ThreadLocal();
 private static Random random=new Random();
    private Student  s=new Student();
 private static int i=1;
 @Override
 public void run() {
  
  print();
 
  
 }
 
 
 
 public void print( ){
  Thread thread=Thread.currentThread();
  System.out.println(thread.getName()+"线程进入");
  
   // Student  s=(Student)threadLocal.get();
 
  
   
  
      s.setAge(i++);
   s.setName("name"+i);
   threadLocal.set(s);
   //System.out.println(thread.getName()+"  "+s.hashCode()+" "+s.getName()+" "+s.getAge() );
      System.out.println(thread.getName()+"线程第一次取得值是 "+((Student)threadLocal.get()).hashCode()+" "+((Student)threadLocal.get()).getName()+" "+((Student)threadLocal.get()).getAge());
  
  
  
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
  
   e.printStackTrace();
  }
  
  System.out.println(thread.getName()+"线程第二次取得值是 "+((Student)threadLocal.get()).hashCode()+" "+((Student)threadLocal.get()).getName()+" "+((Student)threadLocal.get()).getAge());
  
  System.out.println(thread.getName()+"线程退出");
 }
 
 
 
 

 /**
  * @param args
  */
 public static void main(String[] args) {
  ThreadLocalTest2 threadLocal=new ThreadLocalTest2();
     Thread t1=new Thread(threadLocal,"t1");
     Thread t2=new Thread(threadLocal,"t2");
 t1.start();
 t2.start();
 }

 

}

 

打印出:

t1线程进入
t2线程进入
t1线程第一次取得值是 4872882 name2 1
t2线程第一次取得值是 4872882 name3 2
t2线程第二次取得值是 4872882 name3 2
t2线程退出
t1线程第二次取得值是 4872882 name3 2
t1线程退出

看出如果多个线程共享一个对象,改变t2线程中value对象,会同时改变t1线程value对象值.如果把print方法改为如下:

public void print( ){
  Thread thread=Thread.currentThread();
  System.out.println(thread.getName()+"线程进入");
  
  
   
      Student  s=new Student();
  
      s.setAge(i++); 
   s.setName("name"+i);
   threadLocal.set(s);
   //System.out.println(thread.getName()+"  "+s.hashCode()+" "+s.getName()+" "+s.getAge() );
      System.out.println(thread.getName()+"线程第一次取得值是 "+((Student)threadLocal.get()).hashCode()+" "+((Student)threadLocal.get()).getName()+" "+((Student)threadLocal.get()).getAge());
  
  
  
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
  
   e.printStackTrace();
  }
  
  System.out.println(thread.getName()+"线程第二次取得值是 "+((Student)threadLocal.get()).hashCode()+" "+((Student)threadLocal.get()).getName()+" "+((Student)threadLocal.get()).getAge());
  
  System.out.println(thread.getName()+"线程退出");
 }

 

打印出:

t1线程进入
t2线程进入
t2线程第一次取得值是 4872882 name3 2
t1线程第一次取得值是 25724761 name3 1
t1线程第二次取得值是 25724761 name3 1
t1线程退出
t2线程第二次取得值是 4872882 name3 2
t2线程退出

可以看出多个线程如果不共享一个对象,每个线程一个对象的话,一个线程改变value,是不会影响到其他线程的value值,所以ThreadLocal是不能解决多个线程共享一个对象的问题的。ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题,ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
 
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值