ThreadLocal源码分析

目录

1. 线程安全

2.源码剖析

3.补充知识:

4.局限性

1. 线程安全

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

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

方式一:锁(synchronized)

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

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

举个例子:

线程不安全的情况:

private static int num = 0;

public static void main(String[] args) {
    Thread[] threads = new Thread[5];
    for (int i = 0; i < threads.length; i++) {
        threads[i] = new Thread(() -> {
            num += 5;
            System.out.println(Thread.currentThread().getName() + ":" + num);
        }, "thread-" + i);
    }
    for (Thread thread : threads) {
        thread.start();
    }
}

输出结果:

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

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

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

使用ThreadLocal之后:

static ThreadLocal<Integer> num = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
        return 0;
    }
};

public static void main(String[] args) {
    Thread[] threads = new Thread[5];
    for (int i = 0; i < threads.length; i++) {
        threads[i] = new Thread(() -> {
            int localNum = num.get();
            localNum += 5;
            num.set(localNum);
            System.out.println(Thread.currentThread().getName() + ":" + num.get());
        });
    }
    for (Thread thread : threads) {
        thread.start();
    }
}

输出结果:

Thread-0:5
Thread-1:5
Thread-2:5
Thread-3: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()函数中的方法封装比较复杂,我们自己手写一个算法来感受一些这个算法的魅力。

斐波那契数列例子:

public class Demo {
    private static final int HASH_INCREMENT = 0x61c88647;

    public static void main(String[] args) {
        magicHash(16);
        magicHash(32);
    }

    private static void magicHash(int size) {
        int hashCode = 0;
        for (int i = 0; i < size; i++) {
            hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
            System.out.print((hashCode & (size - 1)) + " ");
        }
        System.out.println();
    }
}

执行结果:

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无法保证并发线程中该对象的同步线程安全。

举个例子:

public class Test {
    static App app = new App();
    static ThreadLocal<App> num = ThreadLocal.withInitial(() -> app);

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                App localApp = num.get();
                localApp.inc();
                num.set(localApp);
                System.out.println(Thread.currentThread().getName() + ":" + num.get().getNum());
            });
        }
        for (Thread thread : threads) {
            thread.start();
        }
    }
}
class App {
    private Integer num = 0;

    public void inc() {
        num++;
    }

    public Integer getNum() {
        return num;
    }
}

输出结果:

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ThreadLocal码是Java中一个关键的类,它提供了一种在多线程环境下实现线程本地变量的机制。在JDK 8之前和之后,ThreadLocal的内部结构有所变化。ThreadLocal码分为两部分:ThreadLocal类和ThreadLocalMap类。 ThreadLocal类是一个泛型类,它包含了两个核心方法:set()和get()。set()方法用于将一个值与当前线程关联起来,get()方法用于获取当前线程关联的值。 ThreadLocalMap类是ThreadLocal的内部类,它用于存储每个线程的本地变量。在JDK 8之前,ThreadLocalMap是通过线性探测法解决哈希冲突的,每个ThreadLocal对象都对应一个Entry对象,Entry对象包含了ThreadLocal对象和与之关联的值[2]。 在JDK 8之后,ThreadLocalMap的实现方式发生了改变。使用了类似于HashMap的方式,采用了分段锁的机制来提高并发性能。每个线程维护一个ThreadLocalMap对象,其中的Entry对象也是采用链表的形式来解决哈希冲突。 总结起来,ThreadLocal码主要由ThreadLocal类和ThreadLocalMap类组成。ThreadLocal类提供了set()和get()方法来管理线程本地变量,而ThreadLocalMap类则负责存储每个线程的本地变量,并解决哈希冲突的问题。 史上最全ThreadLocal 详解 ThreadLocal分析_02 内核(ThreadLocalMap) 【JDK码】线程系列之ThreadLocal 深挖ThreadLocal ThreadLocal原理及内存泄露预防 ThreadLocal原理详解——终于弄明白了ThreadLocal ThreadLocal使用与原理 史上最全ThreadLocal 详解。 ThreadLocal分析,主要有ThreadLocal码以及ThreadLocal的内部结构在jdk8前后的变化。 使用方式非常简单,核心就两个方法set/get public class TestThreadLocal { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { threadLocal.set("aaa"); Thread.sleep(500); System.out.println("threadA:" threadLocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { threadLocal.set("bbb"); System.out.println("threadB:" threadLocal.get()); } }).start(); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值