Java 线程03:对ThreadLocal类的理解与运用


ThreadLocal<T> 类 JDK 1.2

java.lang.ThreadLocal<T> ThreadLocal类是为共享变量在每一个线程中创建一个副本,每个线程可以访问自己内部的副本变量。创建一个ThreadLocal类一定是私有的、静态的。
在这里插入图片描述

常用方法

  • public T get()

    获取副本中的value

  • public void set(T value)

    设置副本中的value


1、在Spring中的应用场景

在这里插入图片描述
在Spring中的事务操作就会使用到ThreadLocal类,因为在事务操作过程中,会连接数据库很多次,那么连接数据库就会去连接池获取一个连接对象,为了防止每次获得的连接对象不一样,就使用ThreadLocal类将第一次获取连接对象的时候就将连接对象放入到线程自己的副本中,当这个整个事务操作过程中,就可以保证连接对象一致,保证了对数据库操作的安全性。


2、ThreadLocal运用

目的:让所有线程都对共享变量num进行+5操作,每个线程输出的结果都为5。

不使用ThreadLocal类的线程不安全:

public class Test{
    private int num = 0;

    public void show(){
        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);
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.show();
    }

}

输出:

Thread-0 ===> 5
Thread-1 ===> 10
Thread-2 ===> 15
Thread-4 ===> 20
Thread-3 ===> 25

由于,所有线程都在使用共享变量num,又每一个线程都会对num变量进行+5操作,最后输出的结果值就会出现问题,就会出现线程不安全的问题。

加入ThreadLocal类,实现共享变量成为线程私有的副本:

public class Test{
    // 通过ThreadLocal去封装一个共享变量
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public void show(){
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(()-> {
                // 获取当前线程的threadLocal中的值
                Integer num = threadLocal.get();
                num += 5;
                // 设置当前线程的threadLocal中的值
                threadLocal.set(num);
                System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.show();
    }

}

输出结果:

Thread-0 ===> 5
Thread-2 ===> 5
Thread-1 ===> 5
Thread-3 ===> 5
Thread-4 ===> 5

3、源码分析

Thread.java

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
// 定义了一个ThreadLocal中内部类ThreadLocalMap类型的变量
ThreadLocal.ThreadLocalMap threadLocals = null; 

ThreadLocal.java

 static class ThreadLocalMap {
     /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
     static class Entry extends WeakReference<ThreadLocal<?>> {
         /** The value associated with this ThreadLocal. */
         Object value;

         Entry(ThreadLocal<?> k, Object v) { // Entry内部类
             super(k);
             value = v;
         }
     }
     
     private static final int INITIAL_CAPACITY = 16; 
     private Entry[] table;
     private int size = 0;
     private int threshold; // Default to 0
     private void setThreshold(int len) {
         threshold = len * 2 / 3;
     }
     private static int nextIndex(int i, int len) {
         return ((i + 1 < len) ? i + 1 : 0);
     }
     private static int prevIndex(int i, int len) {
         return ((i - 1 >= 0) ? i - 1 : len - 1);
     }
        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
     //ThreadLocalMap构造函数
     ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 
         table = new Entry[INITIAL_CAPACITY]; // 初始化entry数组的大小 初始大小为16
         int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
         table[i] = new Entry(firstKey, firstValue); // 经过hash算法,找到一个坑,将内容存到对应的数组的位置
         size = 1;
         setThreshold(INITIAL_CAPACITY);
     }
 }

可见ThreadLocal中的内部类ThreadLocalMap类中又有一个内部类Entry类。该Entry类的构造函数,需要传入ThreadLocal实例和value值,两个参数。

// 创建一个ThreadLocal实例
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
  • 通过实例调用get()方法 ----> ThreadLocal.java

    public T get() {
        Thread t = Thread.currentThread(); // 获取调用该方法的当前线程
        ThreadLocalMap map = getMap(t); // 通过当前线程去获取当前线程实例中ThreadLocalMap对象
        if (map != null) { // 如果map不为空
            // 就获取ThreadLocalMap对象中的Entry数组中存储的this为key的entry对象
            // 这里的this对象为调用该方法的ThreadLocal对象
            ThreadLocalMap.Entry e = map.getEntry(this); 
            if (e != null) { // 如果该entry对象不为空,就说明有这个对象的存在
                @SuppressWarnings("unchecked")
                T result = (T)e.value; // 获取entry对象中的值
                return result; // 返回这个值
            }
        }
        // 如果map对象为空,就说明该线程还没有创建ThreadLocalMap对象,该线程变量threadLocals = null
        // 就需要通过setInitialValue(),获取一个map对象并且创建一个新的entry
        return setInitialValue();
    }
    
    
    // getMap()
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    
    // setInitialValue()
    private T setInitialValue() {
        // 调用ThreadLocal类中的initialValue方法,获取默认的初始值null,重写该方法,可以将共享变量设置成初始值,设置成线程私有的副本变量
        T value = initialValue(); 
        Thread t = Thread.currentThread(); // 获取当前线程对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value); // 如果map依旧为空,就创建一个ThreadLocalMap对象
        return value; // 将默认值返回出去
    }
    
    
    // createMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); 
        // 给当前线程创建一个ThreadLocalMap对象,并初始化Entry数组,然后将键值对插入到Entry数组中
    }
    
  • 通过实例调用set()方法 ----> ThreadLocal.java

    public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程对象
        ThreadLocalMap map = getMap(t); // 获取当前线程实例中的ThreadLocalMap对象
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    
    // set
    private void set(ThreadLocal<?> key, Object value) {
    
        // We don't use a fast path as with get() because it is at
        // least as common to use set() to create new entries as
        // it is to replace existing ones, in which case, a fast
        // path would fail more often than not.
    
        Entry[] tab = table; // table是内部类ThreadLocalMap中定义的entry数组
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1); // threadLocal对象经过hash算法,找到数组中的位置
    
        // 该方法遍历entry数组,如果存在key值为threadLocal对象,就覆盖值
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 
            
            ThreadLocal<?> k = e.get();
    
            if (k == key) { // 
                e.value = value;
                return;
            }
    
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
    
        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    

以上源码可以分析出,每一个线程都有一个ThreadLocalMap变量,而ThreadLocalMap是在ThreadLocal类中,而ThreadLocalMap有一个内部类Entry,用于存储ThreadLocal对象和value值的。在ThreadLocalMap中,定义了Entry数组,初始大小为16,可以扩容。该Entry数组中可以存放很多个以ThreadLocal对象为key和共享变量为value的Entry对象。所以,创建了一个ThreadLocal实列,在对应线程中get和set值是针对与该线程操作的,线程之间都是互相隔离,不打扰的。每一个线程都有自己的线程副本。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值