菜鸟笔记
提升您的技术认知

面试题 -ag真人游戏

目录(jdk1.8)

  • 一、什么是threadlocal
  • 二、threadlocal怎么用
  • 三、threadlocal的原理
  • 四、threadlocal源码分析
    • 1.threadlocal的内部属性
    • 2.threadlocal 之 set() 方法
    • 3.threadlocal 之 get() 方法
    • 4.treadlocal的remove方法
    • 5.内部类threadlocalmap的基本结构和源码分析
      • 5.1先看成员和结构部分
      • 5.3threadlocalmap 之 set() 方法
      • 5.4threadlocalmap 之 getentry() 方法
      • 5.5threadlocalmap 之 rehash() 方法
      • 5.6threadlocalmap 之 remove(key) 方法
  • 五、什么情况下threadlocal的使用会导致内存泄漏
  • 六、threadlocal的最佳实践
  • 七、黄金分割 - 魔数0x61c88647
  • 八、总结

threadlocal 是 jdk java.lang 包下的一个类,是天然的线程安全的类,

1.threadloca 是线程局部变量,这个变量与普通变量的区别,在于每个访问该变量的线程,在线程内部都会
初始化一个独立的变量副本,只有该线程可以访问【get() or set()】该变量,threadlocal实例通常声明
为 private static。
2.线程在存活并且threadlocal实例可被访问时,每个线程隐含持有一个线程局部变量副本,当线程生命周期
结束时,threadlocal的实例的副本跟着线程一起消失,被gc垃圾回收(除非存在对这些副本的其他引用)

jdk 源码中解析:

/**
 * this class provides thread-local variables.  these variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code threadlocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user id or transaction id).
 * /

稍微翻译一下:threadlocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问threadlocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。threadlocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户id或事务id)与线程关联起来。

讨论threadlocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈threadlocal的,threadlocal是用在多线程的场景的!!!

threadlocal归纳下来就3类用途:

  1. 保存线程上下文信息,在任意需要的地方可以获取!!!
  2. 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
  3. 线程间数据隔离

1.保存线程上下文信息,在任意需要的地方可以获取!!!
由于threadlocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。

常用的比如每个请求怎么把一串后续关联起来,就可以用threadlocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。

还有比如spring的事务管理,用threadlocal存储connection,从而各个dao可以获取同一connection,可以进行事务回滚,提交等操作。

2.线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!

threadlocal局限性
threadlocal为解决多线程程序的并发问题提供了一种新的思路。但是threadlocal也有局限性,我们来看看阿里规范:

这类场景阿里规范里面也提到了:

threadlocal用法

public class mythreadlocaldemo {
  
	private static threadlocal threadlocal = new threadlocal();
    public static void main(string[] args) throws interruptedexception {
  
        int threads = 9;
        mythreadlocaldemo demo = new mythreadlocaldemo();
        countdownlatch countdownlatch = new countdownlatch(threads);
        for (int i = 0; i < threads; i  ) {
  
            thread thread = new thread(() -> {
  
                threadlocal.set(thread.currentthread().getname());
                system.out.println("threadlocal.get()================>"   threadlocal.get());
                countdownlatch.countdown();
            }, "执行线程 - "   i);
            thread.start();
        }
        countdownlatch.await();
    }
}

代码运行结果:

threadlocal.get()================>执行线程 - 1
threadlocal.get()================>执行线程 - 0
threadlocal.get()================>执行线程 - 3
threadlocal.get()================>执行线程 - 4
threadlocal.get()================>执行线程 - 5
threadlocal.get()================>执行线程 - 8
threadlocal.get()================>执行线程 - 7
threadlocal.get()================>执行线程 - 2
threadlocal.get()================>执行线程 - 6
process finished with exit code 0

threadlocal虽然叫线程局部变量,但是实际上它并不存放任何的信息,可以这样理解:它是线程(thread)操作threadlocalmap中存放的变量的桥梁。它主要提供了初始化、set()、get()、remove()几个方法。这样说可能有点抽象,下面画个图说明一下在线程中使用threadlocal实例的set()和get()方法的简单流程图。

假设我们有如下的代码,主线程的线程名字是main(也有可能不是main):

public class main {
  
    private static final threadlocal local = new threadlocal<>();
    public static void main(string[] args) throws exception{
  
        local.set("doge");
        system.out.println(local.get());
    }
}


上面只描述了单线程的情况并且因为是主线程忽略了thread t = new thread()这一步,如果有多个线程会稍微复杂一些,但是原理是不变的,threadlocal实例总是通过thread.currentthread()获取到当前操作线程实例,然后去操作线程实例中的threadlocalmap类型的成员变量,因此它是一个桥梁,本身不具备存储功能

从thread源码入手:

public class thread implements runnable {
  
......
//与此线程有关的threadlocal值。该映射由threadlocal类维护。
threadlocal.threadlocalmap threadlocals = null;
//与此线程有关的inheritablethreadlocal值。该map由inheritablethreadlocal类维护
threadlocal.threadlocalmap inheritablethreadlocals = null;
......
}

从上面thread类源代码可以看出thread类中有一个threadlocals和一个inheritablethreadlocals 变量,它们都是threadlocalmap类型的变量,默认情况下这两个变量都是null,只有当前线程调用threadlocal类的iset或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是threadlocalmap类对应的get()、set()方法。

1.threadlocal的内部属性

threadlocalmap 的 key 是 threadlocal,但它不会传统的调用 threadlocal 的 hashcode 方法(继承自object 的 hashcode),而是调用 nexthashcode() ,具体运算如下:

public class threadlocal {
  
	//获取下一个threadlocal实例的哈希魔数
	private final int threadlocalhashcode = nexthashcode();
	
	//原子计数器,主要到它被定义为静态
	private static atomicinteger nexthashcode = new atomicinteger();
	
	//哈希魔数(增长数),也是带符号的32位整型值黄金分割值的取正
	private static final int hash_increment = 0x61c88647;
	
	//生成下一个哈希魔数
	private static int nexthashcode() {
  
	    return nexthashcode.getandadd(hash_increment);
	}
	...
}

这里需要注意一点,threadlocalhashcode是一个final的属性,而原子计数器变量nexthashcode和生成下一个哈希魔数的方法nexthashcode()是静态变量和静态方法,静态变量只会初始化一次。换而言之,每新建一个threadlocal实例,它内部的threadlocalhashcode就会增加0x61c88647。举个例子:

//t1中的threadlocalhashcode变量为0x61c88647
threadlocal t1 = new threadlocal();
//t2中的threadlocalhashcode变量为0x61c88647   0x61c88647
threadlocal t2 = new threadlocal();
//t3中的threadlocalhashcode变量为0x61c88647   0x61c88647   0x61c88647
threadlocal t3 = new threadlocal();

threadlocalhashcode是下面的threadlocalmap结构中使用的哈希算法的核心变量,对于每个threadlocal实例,它的threadlocalhashcode是唯一的。

这里写个demo看一下基于魔数 1640531527 方式产生的hash分布多均匀:


public class threadlocaltest {
  
    public static void main(string[] args) {
  
        printallslot(8);
        printallslot(16);
        printallslot(32);
    }
    static void printallslot(int len) {
  
        system.out.println("********** len = "   len   " ************");
        for (int i = 1; i <= 64; i  ) {
  
            threadlocal t = new threadlocal<>();
            int slot = getslot(t, len);
            system.out.print(slot   " ");
            if (i % len == 0) {
  
                system.out.println(); // 分组换行
            }
        }
    }
    /**
     * 获取槽位
     *
     * @param t   threadlocal
     * @param len 模拟map的table的length
     * @throws exception
     */
    static int getslot(threadlocal t, int len) {
  
        int hash = gethashcode(t);
        return hash & (len - 1);
    }
    /**
     * 反射获取 threadlocalhashcode 字段,因为其为private的
     */
    static int gethashcode(threadlocal t) {
  
        field field;
        try {
  
            field = t.getclass().getdeclaredfield("threadlocalhashcode");
            field.setaccessible(true);
            return (int) field.get(t);
        } catch (exception e) {
  
            e.printstacktrace();
        }
        return 0;
    }
}

上述代码模拟了 threadlocal 做为 key 的hashcode产生,看看完美槽位分配:

********** len = 8 ************
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
2 1 0 7 6 5 4 3 
********** len = 16 ************
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3 
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3 
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3 
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3 
********** len = 32 ************
10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 7 14 21 28 3 
10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 7 14 21 28 3 
process finished with exit code 0

2. threadlocal 之 set() 方法

threadlocal中set()方法的源码如下:


  protected t initialvalue() {
  
        return null;
    }
    
   /**
    * 将此线程局部变量的当前线程副本设置为指定值。大多数子类将不需要
    * 重写此方法,而仅依靠{@link #initialvalue} 
    * 方法来设置线程局部变量的值。
    *
    * @param value 要存储在此线程的thread-local副本中的值
    */
   public void set(t value) {
  
    //设置值前总是获取当前线程实例
    thread t = thread.currentthread();
    //从当前线程实例中获取threadlocals属性
    threadlocalmap map = getmap(t);
    if (map != null)
         //threadlocals属性不为null则覆盖key为当前的threadlocal实例,值为value
         map.set(this, value);
    else
    //threadlocals属性为null,则创建threadlocalmap,第一个项的key为当前的threadlocal实例,值为value
        createmap(t, value);
	}
	
	//这里看到获取threadlocalmap实例时候总是从线程实例的成员变量获取
 	threadlocalmap getmap(thread t) {
  
        return t.threadlocals;
    }
    
    //创建threadlocalmap实例的时候,会把新实例赋值到线程实例的threadlocals成员
 	void createmap(thread t, t firstvalue) {
  
        t.threadlocals = new threadlocalmap(this, firstvalue);
    }

上面的过程源码很简单,设置值的时候总是先获取当前线程实例并且操作它的变量threadlocals。步骤是:

  1. 获取当前运行线程的实例。
  2. 通过线程实例获取线程实例成员threadlocals(threadlocalmap),如果为null,则创建一个新的threadlocalmap实例赋值到threadlocals。
  3. 通过threadlocals设置值value,如果原来的哈希槽已经存在值,则进行覆盖。



3.threadlocal 之 get() 方法

threadlocal中get()方法的源码如下:


 	/**
     * 返回此线程局部变量的当前线程副本中的值。如果该变量没有当前线程的值,
     * 则首先通过调用{@link #initialvalue}方法将其初始化为*返回的值。
     *
     * @return 当前线程局部变量中的值
     */
     public t get() {
  
	    //获取当前线程的实例
	    thread t = thread.currentthread();
	    threadlocalmap map = getmap(t);
	    if (map != null) {
  
	    //根据当前的threadlocal实例获取threadlocalmap中的entry,使用的是threadlocalmap的getentry方法
        threadlocalmap.entry e = map.getentry(this);
        if (e != null) {
  
            @suppresswarnings("unchecked")
            t result = (t) e.value;
             return result;
            }
        }
	    //线程实例中的threadlocals为null,则调用initialvalue方法,并且创建threadlocalmap赋值到threadlocals
	    return setinitialvalue();
	}
	
	private t setinitialvalue() {
  
	    // 调用initialvalue方法获取值
	    t value = initialvalue();
	    thread t = thread.currentthread();
	    threadlocalmap map = getmap(t);
	    // threadlocalmap如果未初始化则进行一次创建,已初始化则直接设置值
	    if (map != null)
	        map.set(this, value);
	    else
	        createmap(t, value);
	    return value;
	}
	
	protected t initialvalue() {
  
       return null;
    }

initialvalue()方法默认返回null,如果threadlocal实例没有使用过set()方法直接使用get()方法,那么threadlocalmap中的此threadlocal为key的项会把值设置为initialvalue()方法的返回值。如果想改变这个逻辑可以对initialvalue()方法进行覆盖。

4.treadlocal的remove方法

threadlocal中remove()方法的源码如下:

public void remove() {
  
    //获取thread实例中的threadlocalmap
    threadlocalmap m = getmap(thread.currentthread());
    if (m != null)
       //根据当前threadlocal作为key对threadlocalmap的元素进行移除
       m.remove(this);
}

这里罗列了 threadlocal 的几个public方法,其实所有工作最终都落到了 threadlocalmap 的头上,threadlocal 仅仅是从当前线程取到 threadlocalmap 而已,具体执行,请看下面对 threadlocalmap 的分析。

5.内部类threadlocalmap的基本结构和源码分析

threadlocalmap 是threadlocal 内部的一个map实现,然而它并没有实现任何集合的接口规范,因为它仅供内部使用,数据结构采用 数组 开方地址法,entry 继承 weakreference,是基于 threadlocal 这种特殊场景实现的 map,它的实现方式很值得研究。

threadlocal内部类threadlocalmap使用了默认修饰符,也就是包(包私有)可访问的。threadlocalmap内部定义了一个静态类entry。我们重点看下threadlocalmap的源码,

5.1先看成员和结构部分

/**
 * threadlocalmap是一个定制的散列映射,仅适用于维护线程本地变量。
 * 它的所有方法都是定义在threadlocal类之内。
 * 它是包私有的,所以在thread类中可以定义threadlocalmap作为变量。
 * 为了处理非常大(指的是值)和长时间的用途,哈希表的key使用了弱引用(weakreferences)。
 * 引用的队列(弱引用)不再被使用的时候,对应的过期的条目就能通过主动删除移出哈希表。
 */
static class threadlocalmap {
  
    //注意这里的entry的key为weakreference>
    static class entry extends weakreference> {
  
        //这个是真正的存放的值
        object value;
        // entry的key就是threadlocal实例本身,value就是输入的值
        entry(threadlocal k, object v) {
  
                    super(k);
                    value = v;
        }
    }
    //初始化容量,必须是2的幂次方
    private static final int initial_capacity = 16;
    //哈希(entry)表,必须时扩容,长度必须为2的幂次方
    private entry[] table;
    //哈希表中元素(entry)的个数
    private int size = 0;
    //下一次需要扩容的阈值,默认值为0
    private int threshold;
    //设置下一次需要扩容的阈值,设置值为输入值len的三分之二
    private void setthreshold(int len) {
  
        threshold = len * 2 / 3;
    }
    // 以len为模增加i
    private static int nextindex(int i, int len) {
  
        return ((i   1 < len) ? i   1 : 0);
    }
    // 以len为模减少i
    private static int previndex(int i, int len) {
  
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
}
  1. 这里注意到十分重要的一点:threadlocalmap$entry是weakreference(弱引用),并且键值key为threadlocal实例本身,这里使用了无限定的泛型通配符。
  2. threadlocalmap 的 key 是 threadlocal,但它不会传统的调用 threadlocal 的 hashcode 方法(继承自object 的 hashcode),而是调用 nexthashcode()

5.2接着看threadlocalmap的构造函数

// 构造threadlocal时候使用,对应threadlocal的实例方法void createmap(thread t, t firstvalue)
threadlocalmap(threadlocal firstkey, object firstvalue) {
  
    // 哈希表默认容量为16
    table = new entry[initial_capacity];
    // 计算第一个元素的哈希码
    int i = firstkey.threadlocalhashcode & (initial_capacity - 1);
    table[i] = new entry(firstkey, firstvalue);
    size = 1;
    setthreshold(initial_capacity);
}
// 构造inheritablethreadlocal时候使用,基于父线程的threadlocalmap里面的内容进行
// 提取放入新的threadlocalmap的哈希表中
// 对应threadlocal的静态方法static threadlocalmap createinheritedmap(threadlocalmap parentmap)
private threadlocalmap(threadlocalmap parentmap) {
  
    entry[] parenttable = parentmap.table;
    int len = parenttable.length;
    setthreshold(len);
    table = new entry[len];
    // 基于父threadlocalmap的哈希表进行拷贝
    for (entry e : parenttable) {
  
        if (e != null) {
  
            @suppresswarnings("unchecked")
            threadlocal
网站地图