Java并发编程14——ThreadLocal

  • 概述
    • 与synchronized的区别
  • 用法
  • 源码分析
    • Thread类:ThreadLocalMap threadLocals属性
    • ThreadLocalMap类:ThreadLocal的静态内部类,实际是Entry<ThreadLocal, Object>数组
    • Entry类:ThreadLocalMap的静态内部类,继承弱引用
  • ThreadLocal常用方法
    • get(),set()
    • 延迟构造性质
  • 内存泄漏
    • 为什么Entry要继承弱引用
    • 内存泄漏原因及避免方法
  • InheritabIeThreadLocal

1. 概述

  • 介绍
    • 官方版:ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定
    • 通俗版:一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)
  • ThreadLocal和synchronized的区别:虽然两者都是处理多线程并发访问量的问题,但两者处理问题的角度和思路不同
 synchronizedthreadLocal
原理时间换空间;让不同线程排队访问空间换时间;为每个线程都提供了变量的副本,互不干扰
侧重点多线程之间访问资源的同步多线程间数据相互隔离

2. 用法

  • 创建一个ThreadLocal对象,然后再不同线程中使用get()和set()方法获取该共享变量。
public class Test{

    static ThreadLocal<String> tl = new ThreadLocal<>();

    public static void main(String[] args){       
        Thread one = new Thread(new Runnable(){
            tl.set("111");
        });

        Thread two= new Thread(new Runnable(){
            tl.set("222");
        });

        one.start();
        two.start();
    }
}

3. 源码分析

  • Thread类:每个Thread对象里有一个ThreadLocalMap属性,属性名叫threadLocals
public class Thread implements Runnable {
    ...
    ThreadLocalMap threadLocals;
    ThreadLocalMap inheritableThreadLocals;
    ...
}
  • ThreadLocalMap类:它是ThreadLocal类中的静态内部类
public class ThreadLocal<T> {
    ...
    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        private ThreadLocal.ThreadLocalMap.Entry[] table;    //重要属性
        private int size = 0;
        private int threshold;
        ...
    }
}
  • Entry类:ThreadLocalMap中维护了一个Entry[]的数组(优点类似Hashmap的底层结构),其中Entry类又是ThreadLocalMap中自定义的静态内部类,key是ThreadLocal类,value是obj类
    • 需要注意的是:这个对象继承了 弱引用,涉及到后面的内存泄露问题
static class ThreadLocalMap {

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

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            this.value = v;
        }
    }
}
  • 总结
    • 每个线程(Thread)中都维护了一个ThreadLocalMap的表,实际是一个Entry<ThreadLocal, Object>类的数组
    • eg. 假设共享资源为ThreadLocal<String> threadlocal,在线程t1中调用threadlocal.set("123456")方法时,会先得到t1线程,然后在t1线程中的ThreadLocalMap中添加<key = threadlocal, “123456”>的Entry

4. ThreadLocal类常用方法

(1)set()方法

  • 1. 先获取当前线程t
  • 2. 用ThreadLocal中的getMap方法获取线程t中的ThreadLocalMap对象
  • 3. 如果ThreadLocalMap没被初始化过,进入5初始化;如果已经被初始化过,进入4添加该threadlocal
  • 4. 调用ThreadLocalMap的set()方法添加(该方法类似hashmap中的添加方法)
  • 5. 调用ThreadLocal中的createMap()方法,新建一个ThreadLocalMap并添加该数据和value

(2)get()方法

  • 1. 先获取当前线程t
  • 2. 用ThreadLocal中的getMap方法获取线程t中的ThreadLocalMap对象
    • 情况1:map已被初始化且该entry<threadlocal, value>存在 ——> 返回value
    • 情况2:map已被初始化且该entry<threadlocal, value>不存在——>把entry<threadlocal, null>放入map并返回null
    • 情况3:map没被初始化过——>初始化,把entry<threadlocal, null>放入map并返回null

(3)ThreadLocalMap延迟构造

  • 从上面代码可以看出,只有再第一次调用get()和set()方法时,ThreadLocalMap才会被创建,不使用threadlocal则一直为null

5. 内存泄漏问题

  • 现象:Entry对象继承了弱引用(每次gc都会被垃圾回收)
  • 为什么要继承弱引用?弱引用与内存泄漏现象有关系吗? ——> 答:没关系
    • 如果不继承弱引用,那Entry对象就是一个强引用对象,由于Entry对象组成了threadLocalMap,而后者又与线程的生命周期一样
    • 这种情况下,只要线程一直运行,这两个对象都不会被gc ——> 造成内存泄漏

 

  • 继承弱引用:仍然会存在内存泄漏,但是情况比强引用好
  • 引起内存泄漏原因:由于ThreadLocal的生命周期和Thread一样长,只要没有手动删除key就会一直存在,导致内存泄漏
  • 内存泄漏解决方法
    • 使用完ThreadLocal,调用remove方法删除Entry
    • 使用完ThreadLocal,当前Thread也随之结束


6. 应用场景

  • 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

7. InheritabIeThreadLocal

  • ThreadLocal存在的问题
    • 没有继承性:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的
  • 为了解决上节提出的问题, InheritableThreadLocal 应运而生
    • InheritableThreadLocal 继承自 ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值