下文笔者讲述多线程ThreadLocal功能简介说明,如下所示
ThreadLocal功能: 实现线程间的数据隔离 ThreadLocal在每个线程内部中维护一个变量,其他线程无法访问自己的变量
ThreadLocal简单使用
创建一个简单的User类 public static class User { private int age = 0; public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } 定义ThreadLocalDemo public class ThreadLocalDemo implements Runnable { //创建线程局部变量UserLocal用来保存User对象 private final static ThreadLocal UserLocal = new ThreadLocal(); //对比实现,直接保存到对象里 public User stu = new User(); public void run() { accessUser(); } public void accessUser() { //获取当前线程的名字 String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running!"); //产生一个随机数并打印 Random random = new Random(); int age = random.nextInt(100); System.out.println("thread " + currentThreadName + " set age to:" + age); //获取一个User对象,并将随机数年龄插入到对象属性中 User User = getUser();//getThreadLocalUser User.setAge(age); System.out.println("thread " + currentThreadName + " first read age is:" + User.getAge()); System.out.println("thread " + currentThreadName + " second read age is:" + User.getAge()); } protected User getThreadLocalUser() { //获取本地线程变量并强制转换为User类型 User User = (User) UserLocal.get(); //线程首次执行此方法的时候,UserLocal.get()肯定为null if (User == null) { //创建一个User对象,并保存到本地线程变量UserLocal中 User = new User(); UserLocal.set(User); } return User; } protected User getUser() { return stu; } protected synchronized User getUserSynchronized() { return stu; } } 3. 来测试一下吧 同时运行两个 ThreadLocalDemo 线程。 public static void main(String[] agrs) { ThreadLocalDemo td = new ThreadLocalDemo(); Thread t1 = new Thread(td, "a"); Thread t2 = new Thread(td, "b"); t1.start(); t2.start(); } 调用getUser方法 b is running! a is running! thread b set age to:23 thread a set age to:88 thread b first read age is:23 thread a first read age is:88 thread a second read age is:88 thread b second read age is:88 可以看到直接使用 stu 变量的话,线程 b 第二次读取的时候其实是读取到了线程 a 设置之后的值。 看看放在 ThreadLocal 里面,换成调用 getThreadLocalUser 方法 a is running! b is running! thread a set age to:65 thread b set age to:72 thread b first read age is:72 thread a first read age is:65 thread b second read age is:72 thread a second read age is:65 a,b 两个线程两次都只是各自读取自己的局部变量
ThreadLocal实现原理
ThreadLocal 的 set 方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap类 是ThreadLocal的一个内部类 ThreadLocalMap 内部有一个Entry类 这是一个(key,value)形式的弱引用 为什么要把key作为弱引用呢 是为了让ThreadLocalMap尽可能的小 在 key 不被引用的时候就回收 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 再回到上面的 set 方法 首先是通过getMap(t)方法拿到当前线程ThreadLocalMap对象 Thread 类里面就有一个名为threadLocals的ThreadLocalMap 对象 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } 如果为空,则去创建 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 把新的 ThreadLocalMap 对象赋值给线程t的 threadLocals 对象 看看 ThreadLocalMap 的构造函数 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } table是一个Entry 数组 按照上面的 Entry(ThreadLocal<?> k, Object v) 构造函数 初始化 ThreadLocalMap 对象的时候就会把 ThreadLocal 对象作为key 把value放到一个 Entry 对象中 并且保存到 table 数组中 如果这个对象不为空 则直接调用 ThreadLocalMap 的 set方法 private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); 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(); } 遍历 table 数组 如果当前的 ThreadLocal 对象和其中已经保存的某个 Entry 的 key 相等 则把这个 Entry 的value设置为传进来的值。如果发现key为空(是的,会出现k是null的情况) 所以会接着在replaceStaleEntry重新循环寻找相同的key,找到之后再赋值 所以说我们通过 ThreadLocal 传进来的值 其实都保存在了 ThreadLocalMap 的 Entry对象里 ThreadLocal 的 get 方法 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 就是在Entry中通过当前 ThreadLocal 对象索引到它对应的值 getMap 方法上面已经看过了。看看 ThreadLocalMap 的 getEntry 方法 private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。