ThreadLocal的基本使用,以及它是如何保证线程安全

1.为什么会产生线程安全问题?

在多线程的环境当中,线程之间的变量它一般都是共享的。在堆中会维护共享的数据存储区域,这个区域数据为所有多个线程共享,能够访问并修改变量,所以多线程环境下,可能会存在线程安全问题。

2.使用ThreadLoacl实现线程隔离

ThreadLoacl会为当前线程维护一个变量副本,该变量副本只能被当前线程使用,其它线程无法访问该副本,因此能够解决线程安全问题。

ThreadLocal提供了三个常用方法,一个是set();添加变量;get()获取变量,以及remove()移除变量。

public class HostHolder {
    
    // 创建ThreadLocal,存放user对象
    private ThreadLocal users = new ThreadLocal();

    public void setUser(User user) {
        // 调用set传入user对象
        users.set(user);
    }

    public User getUser(){
        // 调用get()获取user对象
        return users.get();
    }

    public void clearUser() {
        // 移除user对象
        users.remove();
    }
}

上面的user对象,是我在项目中需要保护的资源,user对象存放的是用户的信息,为用户独享,所以需要保证线程安全。

3.ThreadLocal如何保证线程安全?

主要是用到了ThreadLoaclMap对象,这个对象是当前线程维护的变量副本,为当前线程共享。它是一个哈希表,维护了当前线程所有的ThreadLocal对象;hash表的key就是ThreadLocal对象的哈希值,value就是要存储的变量。具体如下图:

ThreadLocal的基本使用,以及它是如何保证线程安全_第1张图片

上述代码中,ThreadLocal users = new ThreadLocal();

hash表的key就是 users对象的hash值,而value就是  users.set(user);方法传入的user对象。

set()方法源码如下:记住一个线程内ThreadLocalMap对象只有一个,初始为null,第一次往里添加元素时才初始化。

    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 因为ThreadLocalMap的对象初始为空,第一次添加元素时才会初始化
        // 所以需要判空
        if (map != null)
            // 如果ThreadLocalMap对象已经创建,就直接将对象存入hash表
            map.set(this, value);
        else
            // 如果还为创建,则初始化ThreadLocalMap对象之后,将需要存储的对象存入hash表
            createMap(t, value);
    }

get()方法:

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // 如果ThreadLocalMap对象存在,就去获取它存储的对象
    if (map != null) {
        // 获取调用者也就users的entry,entry是key-value形式
        ThreadLocalMap.Entry e = map.getEntry(this);
        // entry不为null,就获取它的value并返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果不存在就调用初始化方法
    return setInitialValue();
}

setInitialValue()逻辑和set()方法类似。了解一下,实际上,setInitialValue() 方法是在 get() 方法中被调用,用于初始化变量的初始值并存储到 ThreadLocalMap 中。调用initialValue()方法需要重写,返回你需要存储的变量,作为value进行存储。

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }


    // 创建ThreadLocal,存放user对象
    private ThreadLocal users = new ThreadLocal(){
        // 需要重写initialValue去返回user对象
        @Override
        protected User initialValue() {
            return getUser();
        }
    };
    
    public User getUser(User user) {
        return user;
    }

原理总结:ThreadLocal底层实际上就是为每个线程维护了ThreadLocalMap对象,ThreadLocalMap本质是一个哈希表,它的主要作用是为每个线程提供一个独立的变量副本,这样不同线程之间的变量互不干扰,从而实现线程隔离实现线程隔离,使用时从哈希表中去获取对应的变量。

你可能感兴趣的:(java,开发语言)