博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java ThreadLocal详解
阅读量:6415 次
发布时间:2019-06-23

本文共 4933 字,大约阅读时间需要 16 分钟。

hot3.png

 

    在多线程开发中,ThreadLocal是非常常见的Java API。它可以轻松的创建一个线程安全的变量,使得每一个线程拥有各自的实例变量。

    以下代码为每一个线程分配了一个线程ID。    

public class ThreadId {     // Atomic integer containing the next thread ID to be assigned     private static final AtomicInteger nextId = new AtomicInteger(0);     // Thread local variable containing each thread's ID     private static final ThreadLocal
threadId = new ThreadLocal
() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }

 

ThreadLocal如何做到一个实例存储了不同线程的值?

           跟踪ThreadLocal get()方法可以发现,它先获取了当前线程Thread.currentThread,再从当前线程中获取ThreadLocalMap实例。理一下思路,实际上每一个线程拥有一个ThreadLocalMap实例,这个Map存储了当前线程的值。

        进一步跟踪ThreadLocalMap。它以WeakReference<ThreadLocal>做Key,Object做Value。ThreadLocal做key的作用?为何需要定义成WeakReference?

package com.sd.concurrent;import org.junit.Test;import java.util.concurrent.TimeUnit;/** * Created by sunda on 17-8-23. */public class ThreadLocalTest {    class User {        String name;        String desc;        public User() {        }        public User(String desc, String name) {            this.desc = desc;            this.name = name;        }        public String getDesc() {            return desc;        }        public void setDesc(String desc) {            this.desc = desc;        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }        @Override        public String toString() {            return "ValueHolder{" +                    "desc='" + desc + '\'' +                    ", name='" + name + '\'' +                    '}';        }    }    class Product{        String name;        String desc;        public Product() {        }        public Product(String desc, String name) {            this.desc = desc;            this.name = name;        }        public String getDesc() {            return desc;        }        public void setDesc(String desc) {            this.desc = desc;        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }    }    ThreadLocal
userThreadLocal = new ThreadLocal<>(); ThreadLocal
productThreadLocal = new ThreadLocal<>(); @Test public void test() throws InterruptedException { Thread aaThread1 = new Thread(new Runnable() { @Override public void run() { userThreadLocal.set(new User("aa", "aa11")); productThreadLocal.set(new Product("aa", "aa11")); try { TimeUnit.SECONDS.sleep(1); User valueHolder = userThreadLocal.get(); System.out.println("aa "+valueHolder); } catch (InterruptedException e) { e.printStackTrace(); } } }); aaThread1.start(); Thread bbthread1 = new Thread(new Runnable() { @Override public void run() { userThreadLocal.set(new User("bb", "bb11")); productThreadLocal.set(new Product("bb", "bb11")); try { TimeUnit.SECONDS.sleep(1); User valueHolder = userThreadLocal.get(); System.out.println("bb "+valueHolder); } catch (InterruptedException e) { e.printStackTrace(); } } }); bbthread1.start(); User valueHolder = userThreadLocal.get(); System.out.println("main "+valueHolder); TimeUnit.SECONDS.sleep(300); }}

        以上代码,两个ThreadLocal变量,两个Thread。同一个线程拥有两个ThreadLocal变量的时候,需要互相区分,防止值被覆盖。

        在HashMap中,如果以Object做key,我们都会复写HashCode方法。同样的,在ThreadLocal中,有一个名为threadLocalHashCode的变量,它做为key的hashCode。在设计hashCode的时候,我们会生成一个尽可能稀疏的值,来获取更高读写的性能。

        在ThreadLocal,它以递增一个固定值的方式给ThreadLocal实例分配threadLocalHashCode。该值是精心设计的,在map中可以均匀分布。

        设计成WeakReference是为了尽早的垃圾回收.Thread的创建和销毁都需要耗一定的资源,通常创建完之后会重复使用,往往生命周期很长,而线程中的实例则不然,需要更早的被回收.变量定义成WeakReference将在GC触发后被回收。

          ThreadLocal为我们提供了非常大的便利,但是不正确的使用很容易引起较为隐晦的内存泄漏,这种内存泄漏常见于web程序,例如tomcat下运行的一段代码.            

public class UserIfaceThreadLocal {    public static ThreadLocal
userIfaceThreadLocal = new ThreadLocal<>(); ....... }

           该程序乍看起来毫无问题,前期也能稳定运行,但是过一段时间后,会在永久区溢出.出现这种问题甚至重启web也无济于事.这段"经典"的代码是ClassLoader溢出的范例.

           在分析之前,有两点需要注意:

                1:ThreadLocal实例被申明为static的类变量.它从ClassLoader加载后存活,直到最后Class被回收.

                2:Tomcat作为web容器,它的工作线程存活时间比web程序更长.web程序的所有Class都是被tomat的ClassLoader加载进去的.               

           假设web程序终止,JVM开始回收上述Class,这时候发现有一个ThreadLocal实例的存活.由于tomcat的工作线程并未终止,UserIface仍被线程引用到.于是classloader无法被回收,导致了所有的Class文件仍在JVM中,无法卸载.

            在使用ThreadLocal的时候,需要像使用资源一样去处理,创建出来需要正确"关闭".在上述例子中,需要主动调用remove清空.

                        

        

         

     

 

          

         

 

        

        

    

        

 

        

转载于:https://my.oschina.net/u/992559/blog/1524831

你可能感兴趣的文章
【风马一族_SQL Server】
查看>>
python APScheduler定时任务框架
查看>>
lvs nginx HAProxy优缺点
查看>>
Laravel之认证服务
查看>>
性能测试总结(三)--工具选型篇
查看>>
免费好用的 Apple 工具(Windows 适用)
查看>>
水利行业传感器
查看>>
服务器做网页的搭建
查看>>
linux上部署ant
查看>>
arc073 F many moves(dp + 线段树)
查看>>
HT For Web 拓扑图背景设置
查看>>
长理 校赛的 一个贪心题
查看>>
vuecli3初尝试(转载)
查看>>
学习笔记:索引碎片、计划缓存、统计信息
查看>>
TSQL技巧(一) -- 子查询(subquery)
查看>>
espcms简约版的表单,提示页,搜索列表页
查看>>
KindEditor
查看>>
航拍去浆
查看>>
Linux-shell 练习题(一)
查看>>
12.01个人计划
查看>>