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=线程ID value=变量值 thread-1 1 thread-2 2 thread-3 3 -
那么由此我们可以想到,是否可以自己设计一个类似的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,本质上是新拿了十个篮球,将他们放在同一个柜子里,并且给每个篮球编号,对应的人去获取对应编号篮球,而这本质的上和之前没有什么变化,只是从十个人抢一个篮球,变为了十个人抢存放篮球的柜子的唯一钥匙。
- 首先我们要明确,ThreadLocal的作用是为了保证线程间数据的隔离,那么从这个层面上来说,我们自己实现的 MyThreadLocal是满足要求的,但是它的弊端在于,每个线程都会频繁的去访问企图获取MyThreadLocal,进而去执行对应的获取/设置的操作,举例来说:
-
-
JDK中的ThreadLocal实现---->ThreadLocal源码解析
-
我们从
ThreadLocal
的set()
方法看起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 之后,提供了
- 弱引用
- 是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了
WeakReference
类来实现弱引用。
- 是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了
- 虚引用
- 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了
PhantomReference
类来实现虚引用
- 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了
- 强引用
-
介绍了四种引用和内存回收机制,我们再看一下
ThreadLocal
中Entry
的代码//Entry是弱引用 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> var1, Object var2) { super(var1); this.value = var2; } }
那么结合上面的内容,我们可以得知,当我们不去手动释放
Entry
中的value时,即手动调用ThreadLocal.remove()
时,该堆内存空间在垃圾回收机制工作前不会被释放,内存关系如图所示:
-
但是,为什么其内存占用还是可以维持在一个稳定的状态呢?我们去看一下
get
和set
的代码/* 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; } } }
-
-
-
优秀博客: