当前位置:   article > 正文

ThreadLocal源码分析_firstkey.threadlocalhashcode & (initial_capacity -

firstkey.threadlocalhashcode & (initial_capacity - 1)

目录

1. 线程安全

2.源码剖析

3.补充知识:

4.局限性

1. 线程安全

什么是线程安全? 原子性,可见性,有序性

怎么去解决安全性问题? ->

方式一:锁(synchronized)

方式二:ThreadLocal:提供了一个线程范围的局部变量,线程级别隔离

我们在了解一个技术的时候我们先了解这个技术的使用

举个例子:

线程不安全的情况:

  1. private static int num = 0;
  2. public static void main(String[] args) {
  3.     Thread[] threads = new Thread[5];
  4.     for (int i = 0; i < threads.length; i++) {
  5.         threads[i] = new Thread(() -> {
  6.             num += 5;
  7.             System.out.println(Thread.currentThread().getName() + ":" + num);
  8.         }, "thread-" + i);
  9.     }
  10.     for (Thread thread : threads) {
  11.         thread.start();
  12.     }
  13. }

输出结果:

  1. thread-0:10
  2. thread-2:15
  3. thread-3:20
  4. thread-1:10
  5. thread-4:25

每个线程拿到的num值是不确定的 -> 线程不安全

如何保证线程安全?1)使用synchronized同步锁来解决线程并发安全问题 2)使用ThreadLocal,提供了一个线程范围的局部变量,线程级别隔离。

使用ThreadLocal之后:

  1. static ThreadLocal<Integer> num = new ThreadLocal<Integer>() {
  2.     @Override
  3.     protected Integer initialValue() {
  4.         return 0;
  5.     }
  6. };
  7. public static void main(String[] args) {
  8.     Thread[] threads = new Thread[5];
  9.     for (int i = 0; i < threads.length; i++) {
  10.         threads[i] = new Thread(() -> {
  11.             int localNum = num.get();
  12.             localNum += 5;
  13.             num.set(localNum);
  14.             System.out.println(Thread.currentThread().getName() + ":" + num.get());
  15.         });
  16.     }
  17.     for (Thread thread : threads) {
  18.         thread.start();
  19.     }
  20. }

输出结果:

  1. Thread-0:5
  2. Thread-1:5
  3. Thread-2:5
  4. Thread-3:5
  5. Thread-4:5

2.源码剖析

提出疑问:

1)每个线程的变量副本是如何存储的?

2)ThreadLocal是什么时候设置初始化的?

我们从ThreadLocal的get()方法看起,

进入get()方法之后,我们可以看到在get方法中首先调用了一个getMap()方法。

getMap()方法中返回了一个ThreadLocalMap类型的成员变量threadLocals

因为返回的threadLocals的值为null,我们回到get方法中,调用setInitialValue()方法;

在setInitialValue方法中调用了我们在声明ThreadLocal的时候重写的initialValue()方法;

我们可以看到ThreadLocal本身的initialValue方法返回的是null,我们通过对这个方法的重写,返回了0;

然后,此时value值为0,继续调用getMap方法,依然返回threadLocals,值为null;

进入createMap;

在createMap中将当前ThreadLocal对象作为ThreadLocalMap的key,将初始值0作为ThreadLocalMap的value值,传给ThreadLocalMap的构造函数;

在ThreadLocalMap的构造函数中初始化了一个大小为16的Entry数组用来存放键值对,对于下标的存取使用了一个小算法(斐波那契数列,也叫黄金分割数列),

firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)

总是能产生一个0至(容量-1)的一个随机数,并且不重复;

我们可以看到源码中对于threadLocalHashCode进行初始化的时候调用了nextHashCode函数;

注意是nextHashCode()而不是hashCode(),因为hashCode()函数是存在重复值的,比如“通话”和“重地”;

nextHashCode()函数中使用了这样一个常量--HASH_INCREMENT;

由于nextHashCode()函数中的方法封装比较复杂,我们自己手写一个算法来感受一些这个算法的魅力。

斐波那契数列例子:

  1. public class Demo {
  2. private static final int HASH_INCREMENT = 0x61c88647;
  3. public static void main(String[] args) {
  4. magicHash(16);
  5. magicHash(32);
  6. }
  7. private static void magicHash(int size) {
  8. int hashCode = 0;
  9. for (int i = 0; i < size; i++) {
  10. hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
  11. System.out.print((hashCode & (size - 1)) + " ");
  12. }
  13. System.out.println();
  14. }
  15. }

执行结果:

7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 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

下面我们回到setInitialValue方法,可以看到方法最后返回了初始值0;

将0赋值给了localNum变量;

我们将localNum加5之后再set的时候调用ThreadLocal的set方法,传递值为5的参数;

可以看到在set方法中再次调用了getMap方法;

返回了当前ThreadLocal对象中的threadLocals成员变量,

由于在之前的代码中已经为threadLocals成员变量进行了赋值,所以此时返回了一个ThreadLocalMap对象。

因此,在Thread Local的set方法中直接调用了ThreadLocalMap的set方法;

在ThreadLocalMap的set方法中,我们可以看到依然使用了特殊算法来获取Entry数组的下标,

然后对ThreadLocalMap的Entry数组进行了遍历,如果当前ThreadLocal对象对应的ThreadLocalMap中的key值存在的话,就直接更新key对应的value值,如果不存在的话就new一个新的Entry键值对放到table中,并且如果数量超过16就会进行扩容;

补充:k == null的判断是因为Entry继承了WeakReference,是一个弱应用类型,有可能会为null

set值之后,我们再次get方法获取我们之前的set 的值,

TreadLocal整体的结构图如下所示:

3.补充知识:

引用类型分为:WeakReference弱引用,强引用,软引用,虚引用

我们平时一般使用的都是强应用,

什么是弱应用呢?

举个例子:

A a = new A();

B b = new B(a);

这两个就是属于强引用类型,当a=null的时候,这时垃圾回收器(GC)无法去回收a应用类型占用的响应的堆空间,因为这个a引用和b引用有一种强以来关系,这时我们就需要把B类型设置成WeakReference弱引用类型,这样垃圾回收器就可以正常回收a的对象空间。

4.局限性

ThreadLocal的局限性是,当ThreadLocal中存放的是引用类型的话,我们使用ThreadLocal无法保证并发线程中该对象的同步线程安全。

举个例子:

  1. public class Test {
  2. static App app = new App();
  3. static ThreadLocal<App> num = ThreadLocal.withInitial(() -> app);
  4. public static void main(String[] args) {
  5. Thread[] threads = new Thread[5];
  6. for (int i = 0; i < threads.length; i++) {
  7. threads[i] = new Thread(() -> {
  8. App localApp = num.get();
  9. localApp.inc();
  10. num.set(localApp);
  11. System.out.println(Thread.currentThread().getName() + ":" + num.get().getNum());
  12. });
  13. }
  14. for (Thread thread : threads) {
  15. thread.start();
  16. }
  17. }
  18. }
  19. class App {
  20. private Integer num = 0;
  21. public void inc() {
  22. num++;
  23. }
  24. public Integer getNum() {
  25. return num;
  26. }
  27. }

输出结果:

  1. Thread-1:1
  2. Thread-0:2
  3. Thread-2:3
  4. Thread-3:4
  5. Thread-4:5

因此,ThreadLocal对于引用类型的并发线程安全是不能保证的。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/249227
推荐阅读
相关标签
  

闽ICP备14008679号