Exploring
首页
  • Java

    • 面向对象的思想OOP
    • 浅谈Java反射原理
    • endorsed覆盖JDK中的类
  • 认证与授权

    • LDAP概念和原理介绍
    • OAuth2介绍
  • Impala

    • Impala 介绍
  • MySQL

    • 关于MySQL的一些面试题
    • 解决MySQL不到中文数据
    • 数据库之事务与实现原理
  • Oracle

    • oracle的表空间,用户管理,表操作,函数
    • oracle的查询、视图、索引
    • plsql简单入门
  • Redis

    • 数据类型详解
    • 跳越表
    • 数据持久化的两种方式
  • 共识算法

    • gossip
  • RPC

    • GRPC初识与快速入门
    • ProtocolBuffer基本语法
  • RabbitMQ

    • RabbitMQ入门程序之HelloWorld
    • RabbitMQ之工作模式
  • Zookeeper

    • Zookeeper一文入门
  • Docker

    • Docker入门初体验
  • Maven

    • 把自己的包到Maven中央仓库
    • Maven之自定义插件
  • Nginx

    • nginx的安装
    • nginx的配置文件
    • nignx 的变量
  • Tomcat

    • Servlet3通过SPI进行注册组件
  • Vagrant

    • vagrant 初始化
    • vagrant 常用配置
    • vagrant 自己制作 box
  • Linux

    • 启动方式 Systemd
    • 后台服务
    • 防火墙与 Iptables
  • 设计模式

    • 设计模式-代理
    • 设计模式-单例模式
    • 设计模式-迭代器
  • 分布式

    • CAP 理论
  • 数据结构

    • 数据结构之堆Heap
    • 数据结构之哈希表
    • 数据结构之队列
  • 计算机网络

    • HTTP与HTTPS详解
    • 浅谈DNS协议
    • ISP中的网络层
  • 算法

    • 常用查找算法及Java实现
    • 常用排序算法及Java实现
    • 迪杰斯特拉算法
  • 操作系统

    • 操作系统之进程调度算法
    • 操作系统之进程通讯IPC
    • 操作系统之内存管理
  • 抓包

    • 生成安卓系统证书
  • 加解密

    • 常见加密算法
    • 公开秘钥基础知识
    • RSA 解析
  • Windows

    • scoop 包管理
    • windows-terminal 配置
    • 增强 PowerShell
归档
Github (opens new window)
首页
  • Java

    • 面向对象的思想OOP
    • 浅谈Java反射原理
    • endorsed覆盖JDK中的类
  • 认证与授权

    • LDAP概念和原理介绍
    • OAuth2介绍
  • Impala

    • Impala 介绍
  • MySQL

    • 关于MySQL的一些面试题
    • 解决MySQL不到中文数据
    • 数据库之事务与实现原理
  • Oracle

    • oracle的表空间,用户管理,表操作,函数
    • oracle的查询、视图、索引
    • plsql简单入门
  • Redis

    • 数据类型详解
    • 跳越表
    • 数据持久化的两种方式
  • 共识算法

    • gossip
  • RPC

    • GRPC初识与快速入门
    • ProtocolBuffer基本语法
  • RabbitMQ

    • RabbitMQ入门程序之HelloWorld
    • RabbitMQ之工作模式
  • Zookeeper

    • Zookeeper一文入门
  • Docker

    • Docker入门初体验
  • Maven

    • 把自己的包到Maven中央仓库
    • Maven之自定义插件
  • Nginx

    • nginx的安装
    • nginx的配置文件
    • nignx 的变量
  • Tomcat

    • Servlet3通过SPI进行注册组件
  • Vagrant

    • vagrant 初始化
    • vagrant 常用配置
    • vagrant 自己制作 box
  • Linux

    • 启动方式 Systemd
    • 后台服务
    • 防火墙与 Iptables
  • 设计模式

    • 设计模式-代理
    • 设计模式-单例模式
    • 设计模式-迭代器
  • 分布式

    • CAP 理论
  • 数据结构

    • 数据结构之堆Heap
    • 数据结构之哈希表
    • 数据结构之队列
  • 计算机网络

    • HTTP与HTTPS详解
    • 浅谈DNS协议
    • ISP中的网络层
  • 算法

    • 常用查找算法及Java实现
    • 常用排序算法及Java实现
    • 迪杰斯特拉算法
  • 操作系统

    • 操作系统之进程调度算法
    • 操作系统之进程通讯IPC
    • 操作系统之内存管理
  • 抓包

    • 生成安卓系统证书
  • 加解密

    • 常见加密算法
    • 公开秘钥基础知识
    • RSA 解析
  • Windows

    • scoop 包管理
    • windows-terminal 配置
    • 增强 PowerShell
归档
Github (opens new window)
  • Java

    • 基础

    • 并发与多线程

      • Java并发之Unsafe类
      • JUC工具类之CountDownLatch
      • JUC工具类之CyclicBarrier
      • JUC核心之AQS原理
      • Synchronized与ReentrantLock
      • ThreadLocal与FastThreadLocal
        • ThreadLocal
          • 简介
          • 实现
          • InheritableThreadLocal子线程共享
          • 内存泄漏问题
        • FastThreadLocal
      • volatile关键字
    • 日志系统

    • 单元测试

    • JVM

    • Spring

    • SpringBoot

    • 一些工具

  • 语言
  • Java
  • 并发与多线程
unclezs
2020-12-17
0
目录

ThreadLocal与FastThreadLocal

# ThreadLocal

# 简介

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。 ThreadLocal不能使用基本数据类型,只能使用Object类型。

# 实现

ThreadLocal就是绑定在线程上的,可以让每个线程可以存储隔离的线程安全的数据。

其实现就是在Thread类的本地变量中存储ThreadLocal.ThreadlocalMap一个成员变量。

public class Thread implements Runnable {
   ......(其他源码)
    /* 
     * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
     * 本文主要讨论的就是这个ThreadLocalMap
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
     * 主要用于父子线程间ThreadLocal变量的传递
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后在设置的时候直接使用当前ThreadLocal变量为key到Thread中的ThreadLocalMap中取值,可以看到这里的ThreadlocalMap是一个hash结构。

public class ThreadLocal{
    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                //key为自己声明的threadLocal
                map.set(this, value);
            else
                //创建一个ThreadLocalmap
                createMap(t, value);
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //setInitialValue就是可以在没有设置当前线程的threadlocal的初始值,可以自己定义默认为null
        return setInitialValue();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# InheritableThreadLocal子线程共享

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
1
2
3
4
5
6
7
8
9
10
11

在新建线程的init方法中如果inheritThreadLocals为true则会继承,默认是true.并且是使用InheritableThreadLocal的才能继承,判断条件就是thread.inheritableThreadLocals是不是为null,而InheritableThreadLocal.createMap就是将threadlocalMap重写为了inheritableThreadLocals

示例

@Slf4j
public class InheritableThreadLocalSample {
  private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
  private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

  public static void main(String[] args) {
    Thread parent = new Thread(() -> {
      threadLocal.set("uncle");
      inheritableThreadLocal.set("uncle");
      log.info("父线程的值:threadLocal:{},inheritableThreadLocal:{}", threadLocal.get(),inheritableThreadLocal.get());
      Thread child = new Thread(() -> {
        log.info("父线程的值:threadLocal:{},inheritableThreadLocal:{}", threadLocal.get(),inheritableThreadLocal.get());
      },"child thread");
      child.start();
    },"parent thread");
    parent.start();
  }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

输出

2020-12-17 17:11:59 [parent thread] INFO  com.unclezs.samples.java.thread.threadlocal.InheritableThreadLocalSample #lambda$main$1:18 - 父线程的值:threadLocal:uncle,inheritableThreadLocal:uncle
2020-12-17 17:11:59 [child thread] INFO  com.unclezs.samples.java.thread.threadlocal.InheritableThreadLocalSample #lambda$null$0:20 - 父线程的值:threadLocal:null,inheritableThreadLocal:uncle
1
2

# 内存泄漏问题

使用的时候注意ThreadLocal用完之后没用的要释放掉,以免造成内存泄漏。为什么不释放就可能照成内存泄漏呢?JVM不会回收吗?

首先看看ThreadLocalMap的结构,也是哈希结构,不过是开放地址法解决冲突的

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private void set(ThreadLocal<?> key, Object value) {
        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)]) {
            ThreadLocal<?> k = e.get();
            //已经存在直接设置value
            if (k == key) {
                e.value = value;
                return;
            }
            //entity存在,但是弱引用key threadlocal已经被回收了 获取到的key为null
            if (k == null) {
                //直接覆盖这个已经被回收的key的位置 放入新的
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        //不存在冲突
        tab[i] = new Entry(key, value);
        int sz = ++size;
        //先清理已经被回收了的,范围是keyindex->size 判断是否需要扩容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

所以这里可以看出来,首先Entity是个弱引用对象,引用对象为ThreadLocal,所以ThreadLocal只要发生了GC了就会被回收,这个时候Entity中的样子就变成了:

key=null,value=xx

虽然key threadlocal被回收了,但是value并没有被回收,而且entity也还是在table数组中强引用占用着内存。所以如果不处理这些数据就有可能造成内存泄漏

你可以想象,我们平时使用线程池,这个线程复用技术,因为ThreadLocal实现还是在Thread中放一个ThreadLocalMap,如果复用了Thread也代表会重复用这个ThreadLocalMap,如果一个线程在使用完ThreadLocal之后,另一个线程又拿过来用,导致ThreadLocalMap里面的一直没有正常释放,甚至越放越多。最终导致每个线程里面的ThreadLocalMap都有大量数据,导致内存泄漏

虽然ThreadLocalMap会在set,get以及resize等方法中对stale slots做自动删除(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)。但是最佳的实践还是用put就有对应的remove才行

比如用户登录进行put,用户注销进行remove。

# FastThreadLocal

这个为什么说快呢?难道Hash存取数据还不够快吗?还有更快的吗?那也只有数组了。

这个实现是在Netty中的,用数组代替了 Hash+开发地址法。因为ThreadLocalMap是放在线程的成员变量中的,所以Netty自己继承出来一个FastThread,里面就包含了个InternalThreadLocalMap,别看名字是map其实是数组实现的。

所以具体要使用FastThreadLocal需要使用FastThread才行。

更多了解去 Netty进阶:自顶向下解析FastThreadLocal (opens new window)

在 GitHub 编辑此页 (opens new window)
上次更新: 2024/02/25, 12:11:11
Synchronized与ReentrantLock
volatile关键字

← Synchronized与ReentrantLock volatile关键字→

Theme by Vdoing | Copyright © 2018-2024 unclezs
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式