栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal

目录

场景:

 实现:

那么ThreadLocalMap又是啥呢?

ThreadLocalMap的底层结构

那么ThreadLocalMap为什么要用数组呢?

那么set对象存放在哪里?

如何共享ThreadLocal数据,让多个线程访问

内存泄露问题

ThreadLocal练习


前置:

 ThreadLocal——线程本地存储。也就是线程本地变量的管理者,一般用于实现线程隔离(数据隔离),在线程中使用它存储数据,仅在当前线程访问使用

其实,它仅仅是一个管理者,真正存储数据的不是它,而是一个叫做ThreadLocalMap的对象,每个线程都有一个自己的TreadLocalMap对象(在Thread中对象名为:threadLocals或者inheritableThreadLocals)。

场景:

比如JDBC,我们每个线程进行数据库操作,连接都有自己独有的,而不会出现a连接到b上面

 实现:

这里我们会体现他是怎么做到线程隔离的

ThreadLocal localName = new ThreadLocal();
localName.set("张三");
String name = localName.get();
localName.remove();

set源代码

会发现,设置值->会先获取当前线程,根据当前线程得到它的ThreadLocalMap,然后通过ThreadLocalMap赋值

那么ThreadLocalMap又是啥呢?

从源码可知,ThreadLocalMap其实就是Thread中的一个叫threadLocals的变量获取的

所以说,数据的逻辑是在Thread当前线程中,而不是在ThreadLocal,它相当于只是一个管理类

public void set(T value) {
    Thread t = Thread.currentThread();// 获取当前线程
    ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
    if (map != null) // 校验对象是否为空
        map.set(this, value); // 不为空set
    else
        createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以发现每个Thread都维护自己的ThreadLocals变量,当每个线程创建ThreadLocal时候,实际上数据是存在ThreadLocals里面的,从而实现隔离,所以说数据隔离的逻辑在Thread中;

public class Thread implements Runnable {
      ……

    
    ThreadLocal.ThreadLocalMap threadLocals = null;

    
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  
     ……

ThreadLocalMap的底层结构

类似于HashMap,其实不然,源码可知是一个Entry->就是一个键值对对象,继承WeakReference弱引用,key是ThreadLocal的引用,但是存储数据的逻辑是给到Thread本身,就像在公司做事一样,我现在在这个公司做事,我是Thread,事情是value,在的地方是ThreadLocal

(22条消息) Java中Map集合中的Entry对象_宇智波爱编程的博客-CSDN博客_entry

static class ThreadLocalMap {

        static class Entry extends WeakReference> {
            
            Object value;

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

其实可以发现,本身ThreadLocalMap维护的是一个哈希表一样的结构,本质也就是数组

那么ThreadLocalMap为什么要用数组呢?

之前我们说过,ThreadLocal就像是维护了一个地方,存储值的逻辑还是在Thread本身,将值存在ThreadLocalMap中,而ThreadLocalMap维护的是一个哈希表,每个Entry都是一个键值对,键就是在哪做事(ThreadLocal),对就是值,所以说我们Thread是可以做很多事的,做的地方也可以不一样(可以创建多个ThreadLocal对象),只是说逻辑都在当前Thread本身上,所以肯定用数组来存;

场景:开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里;

 static class ThreadLocalMap {

       
        static class Entry extends WeakReference> {
            
            Object value;

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


        private static final int INITIAL_CAPACITY = 16;

      
        private Entry[] table;

那么set对象存放在哪里?

(22条消息) JVM-01(阶段性学习)_Fairy要carry的博客-CSDN博客

因为是线程,众所周知,每个线程都会有自己的栈内存,,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

注:当然我们new 的ThreadLocal实例虽然被Thread给引用,进行了一个持有的操作,但是new嘛,所以也是位于堆上的;

如何共享ThreadLocal数据,让多个线程访问

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

private void test() {    
final ThreadLocal threadLocal = new InheritableThreadLocal();       
threadLocal.set("帅得一匹");    
Thread t = new Thread() {        
    @Override        
    public void run() {            
      super.run();            
      Log.i( "张三帅么 =" + threadLocal.get());        
    }    
  };          
  t.start(); 
} 

内存泄露问题

我们首先回顾一下ThreadLocalMap,里面的Entry继承了WeakReference弱引用

说明ThreadLocal在保存数据时会吧自己当做key存在ThreadLocalMap中,也就是说ThreadLocal会被ThreadLocalMap持有引用(也就是Thread当前线程),正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了

 (22条消息) 垃圾回收流程-简单叙述_Fairy要carry的博客-CSDN博客_垃圾回收过程

导致问题:

ThreadLocal因为在Thread本身逻辑里面是没有被强引用的,所以发生GC时候会被回收,但是value会暴露出来

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露;

如何解决:

最后remove即可,就可以把值给清空了

ThreadLocal localName = new ThreadLocal();
try {
    localName.set("张三");
    ……
} finally {
    localName.remove();
}

remove逻辑:把所有对应的值都置空

  private void remove(ThreadLocal key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

那么问题来了,为什么要把ThreadLocalMap的key设计成弱引用?

key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。

ThreadLocal练习
        //主线程中
		ThreadLocal k1 = new ThreadLocal<>();
		ThreadLocal k2 = new ThreadLocal<>();
		ThreadLocal k3 = new ThreadLocal<>();
		k1.set("main thread");
		k2.set(1000);
		k3.set("hallo~");
		
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("thread1:"+k1.get());
				System.out.println("thread1:"+k2.get());
				System.out.println("thread1:"+k3.get());
 
				k1.set("123");
				k2.set(1);
				k3.set("sss");
 
				System.out.println("thread1:"+k1.get());
				System.out.println("thread1:"+k2.get());
				System.out.println("thread1:"+k3.get());
				System.out.println();
			}
		});
 
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
 
				System.out.println("thread2:"+k1.get());
				System.out.println("thread2:"+k2.get());
				System.out.println("thread2:"+k3.get());
 
				k1.set("abc");
				k2.set(2);
				k3.set(k3);
 
				System.out.println("thread2:"+k1.get());
				System.out.println("thread2:"+k2.get());
				System.out.println("thread2:"+k3.get());
				System.out.println();
			}
		});
 
		Thread thread3 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
 
				System.out.println("thread3:"+k1.get());
				System.out.println("thread3:"+k2.get());
				System.out.println("thread3:"+k3.get());
 
				k1.set("haha");
				k2.set(10);
				k3.set(new Object());
 
				System.out.println("thread3:"+k1.get());
				System.out.println("thread3:"+k2.get());
				System.out.println("thread3:"+k3.get());
				System.out.println();
			}
		});
 
		thread1.start();
		thread2.start();
		thread3.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main:"+k1.get());
		System.out.println("main:"+k2.get());
		System.out.println("main:"+k3.get()); 

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号