ThreadLocal浅析

ThreadLocal

  • 作用:
    • ThreadLocal可以让每个线程拥有一个属于自己的变量副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离
  • 方法介绍:

    • public void set(Object value)
      • 设置当前线程的线程局部变量的值
    • public Object get()
      • 该方法返回当前线程所对应的线程局部变量
    • public void remove()
      • 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度
    • protected Object initialValue()
      • 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null
  • 使用:
    • 未使用ThreadLocal

      package com.example.lib;
      
      //该例子的本意是想要开启三个线程,每个线程有自己的数据备份,互不干扰,即最终的结果为:
      //Thread0 count == 0
      //Thread1 count == 1
      //Thread2 count == 2
      
      public class userThreadLocal {
          public static int count = 0;
      
          public void startThread() {
              Thread[] utable = new Thread[3];
              for (int i = 0; i < 3; i++) {
                  utable[i] = new Thread(new userThread(i));
              }
      
              for (int i = 0; i < 3; i++) {
                  utable[i].start();
              }
          }
      
          class userThread implements Runnable {
              private int id;
      
              public userThread(int i) {
                  this.id = i;
              }
              @Override
              public void run() {
                  System.out.println("before Thread name = " + Thread.currentThread().getName() + ", count = " + count);
                  count += id;
                  System.out.println("after Thread name = " + Thread.currentThread().getName() + ", count = " + count);
              }
          }
      
          public static void main(String[] args) {
              userThreadLocal userThreadLocal = new userThreadLocal();
              userThreadLocal.startThread();
          }
      }
      
    • 使用ThreadLocal

      package com.example.lib;
      
      //使用了ThreadLocal之后,发现达成了我们的目的,三个线程中存在的都是初始值为0的ThreadLocal变量,不会相互影响,结果为
      //Thread0 count == 0
      //Thread1 count == 1
      //Thread2 count == 2
      
      public class userThreadLocal {
          public static ThreadLocal<Integer> userLocal = new ThreadLocal<Integer>() {
                  @Override
                  protected Integer initialValue() {
                      return 0;
                  }
          };
      
          public void startThread() {
              Thread[] utable = new Thread[3];
              for (int i = 0; i < 3; i++) {
                  utable[i] = new Thread(new userThread(i));
              }
      
              for (int i = 0; i < 3; i++) {
                  utable[i].start();
              }
          }
      
          class userThread implements Runnable {
              private int id;
      
              public userThread(int i) {
                  this.id = i;
              }
              @Override
              public void run() {
                  Integer count = userLocal.get();
                  System.out.println("before Thread name = " + Thread.currentThread().getName() + ", count = " + count);
                  count += id;
                  userLocal.set(count);
                  System.out.println("after Thread name = " + Thread.currentThread().getName() + ", count = " + count);
              }
          }
      
          public static void main(String[] args) {
              userThreadLocal userThreadLocal = new userThreadLocal();
              userThreadLocal.startThread();
          }
      }
      
  • 进一步理解ThreadLocal
    • 根据ThreadLocal的定义,本质上就是让每个线程之间的数据相互隔离,以线程ID作为KEY,将数据存储起来,如下图所示:

      Key=线程IDvalue=变量值
      thread-11
      thread-22
      thread-33
    • 那么由此我们可以想到,是否可以自己设计一个类似的ThreadLocal:使用HashMap,以 Thread-ID作为 KEY,存入对应的 Value

      //我们自己创建的ThreadLocal
      class MyThreadLocal<T> {
      
          //声明HaspMap变量,以Thread为KEY,进行数据存储
          public Map<Thread, T> ThreadMap = new HashMap<>();
      
          //为了保证访问时的安全,将其加上对象锁
          public synchronized void set( T value) {
              ThreadMap.put(Thread.currentThread(), value);
          }
      
          public synchronized T get() {
              return ThreadMap.get(Thread.currentThread());
          }
      
      }
      
      class UseThreadLocal implements Runnable{
      
          //使用JDK的ThreadLocal
          public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
          public static ThreadLocal<String>  threadlocal2 = new ThreadLocal<>();
          //使用我们自己设计的ThreadLocal
          public static MyThreadLocal<Integer> myThreadLocal = new MyThreadLocal<>();
          public int id;
      
          public UseThreadLocal() {}
          public UseThreadLocal(int value) {
              id = value;
          }
      
          //创建三个线程
          public void stratThread() {
              for (int i = 0; i < 3; i++) {
                  new Thread(new UseThreadLocal(i)).start();
              }
          }
      
          //在线程中对ThreadLocal赋值
          @Override
          public void run() {
              myThreadLocal.set(id);
              //当线程2执行时,增加一个threadlocal2的值
              if (id == 2) {
                  threadLocal2.set("this is Threadlocal 2");
              }
              System.out.println("This Thread = " + Thread.currentThread().getName() +
                      ", value is = " + myThreadLocal.get());
          }
          
          public static void main(String[] args) {
              UseThreadLocal useThreadLocal = new UseThreadLocal();
              useThreadLocal.stratThread();
          }
      }
      

      通过上述程序,可以发现我们自己设计的ThreadLocal同样可以达到JDK中的ThreadLocal一样的效果,但是我们自己设计的ThreadLocal有什么弊端呢?

    • MyThreadLocal的弊端:

      • 首先我们要明确,ThreadLocal的作用是为了保证线程间数据的隔离,那么从这个层面上来说,我们自己实现的 MyThreadLocal是满足要求的,但是它的弊端在于,每个线程都会频繁的去访问企图获取MyThreadLocal,进而去执行对应的获取/设置的操作,举例来说:
        • 有十个人在抢一个篮球,JDK中的ThreadLocal的做法是:给每个人分配一个篮球,从而保证不再去抢一个篮球;而我们自己设计的MyThreadLocal,本质上是新拿了十个篮球,将他们放在同一个柜子里,并且给每个篮球编号,对应的人去获取对应编号篮球,而这本质的上和之前没有什么变化,只是从十个人抢一个篮球,变为了十个人抢存放篮球的柜子的唯一钥匙。
  • JDK中的ThreadLocal实现---->ThreadLocal源码解析
    • 我们从ThreadLocalset()方法看起

      public void set(T var1) {
          //首先,我们会获取当前的线程
          Thread var2 = Thread.currentThread();
          //然后去根据当前线程获取对应的ThreadLocalMap -----> 那么这个ThreadLocalMap又是什么呢?
          ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
          if (var3 != null) {
              //如果当前线程存在ThreadLocalMap,那么就将线程号(var2)作为key,传入的参数var1作为值,设置到对应的ThreadLocalMap中
              var3.set(this, var1);
          } else {
              //如果当前线程没有ThreadLocalMap,则创建一个ThreadLocalMap,并把线程var2作为key,传入的参数var1作为值,设置到当前线程新创建的ThreadLocalMap中
              this.createMap(var2, var1);
          }
      }
      
      //我们调用的getMap
      ThreadLocal.ThreadLocalMap getMap(Thread var1) {
          //返回一个threadLocals变量,因为,我们是通过Thread(var1)去获取的,查找发现,这个threadLocals是在Thread类中的成员变量
      	return var1.threadLocals;
      }
      
      //threadLoacals变量是在Thread类中的
      public class Thread implements Runnable {
          ....
      	ThreadLocalMap threadLocals = null;
          ....
      }
      
    • 通过set()方法,我们发现最终是将当前线程作为key,传入的参数作为值,传到了ThreadLocalMap中,那么ThreadLocalMap又是什么呢?

      static class ThreadLocalMap {
      	private static final int INITIAL_CAPACITY = 16;
          //这个Entry[]就是最终保存数据的地方,而为什么此处要定义为数组,是因为一个类中可能存在多个ThreadLocal,所以对应的数据需要用不同的Entry进行存储,所以需要定义为数组类型
       	private ThreadLocal.ThreadLocalMap.Entry[] table;
      	private int size;
          private int threshold;
      
          private void setThreshold(int var1) {
          	this.threshold = var1 * 2 / 3;
      	}
      
      	private static int nextIndex(int var0, int var1) {
      		return var0 + 1 < var1 ? var0 + 1 : 0;
      	}
      
      	private static int prevIndex(int var0, int var1) {
               return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
      	}
      
          //查看ThreadLocalMap的构造函数,发现我们是将key\value通过Entry保存了起来 --> 那么这个Entry又是什么呢?
          ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
          	this.size = 0;
              this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
              //通过threadLocalHashCode判断是否相同类型
              int var3 = var1.threadLocalHashCode & 15;
              this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
              this.size = 1;
              this.setThreshold(16);
      	}
          ......
      }
      
      //Entry是一个类
      static class Entry extends WeakReference<ThreadLocal<?>> {
      	Object value;
      
          //Entry将ThreadLocal和value就保存了起来
      	Entry(ThreadLocal<?> var1, Object var2) {
      		super(var1);
      		this.value = var2;
      	}
      }
      
    • 我们再来看一下get()方法

      public T get() {
          //首先获取到当前的Thread
      	Thread var1 = Thread.currentThread();
          //通过当前的Thread ID去获取对应的ThreadLocalMap
      	ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
      	if (var2 != null) {
              //如果当前的ThreadLocalMap不为空,那么再根据当前Thread ID去获取对应ThreadLocalMap中的Entry
          	ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
      		if (var3 != null) {
                  //如果Entry不为空,那么拿到Entry中的value
      			Object var4 = var3.value;
      			return var4;
      		}
      	}
      
      	return this.setInitialValue();
      }
      
      //通过getMap拿到了对应Thread的threadLocals
      ThreadLocal.ThreadLocalMap getMap(Thread var1) {
      	return var1.threadLocals;
      }
      
    • 根据上面的源码分析,我们可以得到这样的类保存关系:

      class Thread {
          ThreadLocalMap threadLocals;
      }
      
      class ThreadLocalMap {
          Entry[] table;
      }
      
      class Entry {
          ThreadLocal<?> k;
          Object v;
      }
      

      在这里插入图片描述
      在这里插入图片描述

  • ThreadLocal造成内存泄漏
    • ThreadLocal使用不当会造成内存泄漏,具体看例子

      public class ThreadLocalOOM {
          private static final int TASK_LOOP_SIZE = 500;
      
          final static ThreadPoolExecutor poolExecutor
                = new ThreadPoolExecutor(5, 5,
                  1,
                TimeUnit.MINUTES,
                  new LinkedBlockingQueue<>());
      
          static class LocalVariable {
              private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
          }
      
          final static ThreadLocal<LocalVariable> localVariable
                  = new ThreadLocal<>();
      
          public static void main(String[] args) throws InterruptedException {
            Object o = new Object();
              /*5*5=25*/
              for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
                  poolExecutor.execute(new Runnable() {
                      public void run() {
                          //localVariable.set(new LocalVariable());
                          new LocalVariable();
                          System.out.println("use local varaible");
                          //localVariable.remove();
                      }
                  });
      
                Thread.sleep(100);
              }
            System.out.println("pool execute over");
          }
      }
      
    • 泄漏原因

      • 首先我们介绍一下JAVA中的四种引用

        • 强引用
          • 凡是通过关键字new定义出来的都是强引用,强引用在栈空间没释放时,堆内存不会被回收释放
        • 软引用
          • 用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
        • 弱引用
          • 是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。
        • 虚引用
          • 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用
      • 介绍了四种引用和内存回收机制,我们再看一下 ThreadLocalEntry 的代码

        //Entry是弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
                    Object value;
        
                    Entry(ThreadLocal<?> var1, Object var2) {
                        super(var1);
                        this.value = var2;
                    }
                }
        

        那么结合上面的内容,我们可以得知,当我们不去手动释放Entry中的value时,即手动调用 ThreadLocal.remove() 时,该堆内存空间在垃圾回收机制工作前不会被释放,内存关系如图所示:
        在这里插入图片描述

      • 但是,为什么其内存占用还是可以维持在一个稳定的状态呢?我们去看一下 getset 的代码

        /* set方法 */
        public void set(T var1) {
        	Thread var2 = Thread.currentThread();
        	ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        	if (var3 != null) {
                //看ThreadLocalMap的set
        		var3.set(this, var1);
        	} else {
        		this.createMap(var2, var1);
        	}
        }
        
        private void set(ThreadLocal<?> var1, Object var2) {
        	ThreadLocal.ThreadLocalMap.Entry[] var3 = this.table;
        	int var4 = var3.length;
        	int var5 = var1.threadLocalHashCode & var4 - 1;
            
        	for(ThreadLocal.ThreadLocalMap.Entry var6 = var3[var5]; var6 != null; var6 = var3[var5 = nextIndex(var5, var4)]) {
        		ThreadLocal var7 = (ThreadLocal)var6.get();
                 if (var7 == var1) {
        			var6.value = var2;
        			return;
        		}
        
        		if (var7 == null) {
        			//此处有一个cleanSomeSlots方法,该方法就会清理一部份内存
        			this.replaceStaleEntry(var1, var2, var5);
        			return;
        		}
        	}
        
        	var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
        	int var8 = ++this.size;
        	if (!this.cleanSomeSlots(var5, var8) && var8 >= this.threshold) {
        		this.rehash();
        	}
        }
        
        /* get方法 */
        public T get() {
        	Thread var1 = Thread.currentThread();
        	ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
        	if (var2 != null) {
                //看ThreadLocalMap的get方法
        		ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
        		if (var3 != null) {
        			Object var4 = var3.value;
        			return var4;
        		}
        	}
        	return this.setInitialValue();
        }
        
        private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {
        	int var2 = var1.threadLocalHashCode & this.table.length - 1;
        	ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];
        	return var3 != null && var3.get() == 
        		var1 ? var3 : this.getEntryAfterMiss(var1, var2, var3);
        }
        
        private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> var1, int var2, ThreadLocal.ThreadLocalMap.Entry var3) {
        	ThreadLocal.ThreadLocalMap.Entry[] var4 = this.table;
        	for(int var5 = var4.length; var3 != null; var3 = var4[var2]) {
        		ThreadLocal var6 = (ThreadLocal)var3.get();
                 if (var6 == var1) {
                 	return var3;
        		}
        		if (var6 == null) {
                  //此处有一个expungeStaleEntry方法,该方法就会清理一部份内存
        			this.expungeStaleEntry(var2);
        		} else {
        			var2 = nextIndex(var2, var5);
        		}
        	}
        
        	return null;
        }
        
        
        /* remove方法 */
        public void remove() {
        	ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
        	if (var1 != null) {
        		var1.remove(this);
        	}
        }
        
        private void remove(ThreadLocal<?> var1) {
        	ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
        	int var3 = var2.length;
        	int var4 = var1.threadLocalHashCode & var3 - 1;
        	for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
        		if (var5.get() == var1) {
        			var5.clear();
                    //本质上调用expungeStaleEntry进行内存清除
        			this.expungeStaleEntry(var4);
        			return;
        		}
        	}
        }
        
  • 优秀博客:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值