ThreadLocal是线程变量,ThreadLocal中填充的变量属于当前线程,该变量对其余线程而言是隔离的。ThreadLocal为变量在每一个线程中都建立了一个副本,那么每一个线程能够访问本身内部的副本变量。数据库
它具备3个特性:数组
在不使用ThreadLocal的状况下,变量不隔离,获得的结果具备随机性。安全
public class Demo { private String variable; public String getVariable() { return variable; } public void setVariable(String variable) { this.variable = variable; } public static void main(String[] args) { Demo demo = new Demo(); for (int i = 0; i < 5; i++) { new Thread(()->{ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); }).start(); } } }
输出结果:多线程
Thread-2 Thread-2 Thread-4 Thread-4 Thread-1 Thread-2 Thread-0 Thread-2 Thread-3 Thread-3
在不使用ThreadLocal的状况下,变量隔离,每一个线程有本身专属的本地变量variable,线程绑定了本身的variable,只对本身绑定的变量进行读写操做。并发
public class Demo { private ThreadLocal<String> variable = new ThreadLocal<>(); public String getVariable() { return variable.get(); } public void setVariable(String variable) { this.variable.set(variable); } public static void main(String[] args) { Demo demo = new Demo(); for (int i = 0; i < 5; i++) { new Thread(()->{ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); }).start(); } } }
输出结果:ide
Thread-0 Thread-0 Thread-1 Thread-1 Thread-2 Thread-2 Thread-3 Thread-3 Thread-4 Thread-4
上述需求,经过synchronized加锁一样也能实现。可是加锁对性能和并发性有必定的影响,线程访问变量只能排队等候依次操做。TreadLocal不加锁,多个线程能够并发对变量进行操做。函数
public class Demo { private String variable; public String getVariable() { return variable; } public void setVariable(String variable) { this.variable = variable; } public static void main(String[] args) { Demo demo = new Demo1(); for (int i = 0; i < 5; i++) { new Thread(()->{ synchronized (Demo.class){ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); } }).start(); } } }
ThreadLocal和synchronized都是用于处理多线程并发访问资源的问题。ThreadLocal是以空间换时间的思路,每一个线程都拥有一份变量的拷贝,从而实现变量隔离,互相不干扰。关注的重点是线程之间数据的相互隔离关系。synchronized是以时间换空间的思路,只提供一个变量,线程只能经过排队访问。关注的是线程之间访问资源的同步性。ThreadLocal能够带来更好的并发性,在多线程、高并发的环境中更为合适一些。高并发
JDBC对于事务原子性的控制能够经过setAutoCommit(false)设置为事务手动提交,成功后commit,失败后rollback。在多线程的场景下,在service层开启事务时用的connection和在dao层访问数据库的connection应该要保持一致,因此并发时,线程只能隔离操做自已的connection。源码分析
解决方案1:service层的connection对象做为参数传递给dao层使用,事务操做放在同步代码块中。性能
存在问题:传参提升了代码的耦合程度,加锁下降了程序的性能。
解决方案2:当须要获取connection对象的时候,经过ThreadLocal对象的get方法直接获取当前线程绑定的链接对象使用,若是链接对象是空的,则去链接池获取链接,并经过ThreadLocal对象的set方法绑定到当前线程。使用完以后调用ThreadLocal对象的remove方法解绑链接对象。
ThreadLocal的优点:
每一个ThreadLocal维护一个ThreadLocalMap,Map的Key是ThreadLocal实例自己,value是要存储的值。
每一个线程内部都有一个ThreadLocalMap,Map里面存放的是ThreadLocal对象和线程的变量副本。Thread内部的Map经过ThreadLocal对象来维护,向map获取和设置变量副本的值。不一样的线程,每次获取变量值时,只能获取本身对象的副本的值。实现了线程之间的数据隔离。
JDK1.8的设计相比于以前的设计(经过ThreadMap维护了多个线程和线程变量的对应关系,key是Thread对象,value是线程变量)的好处在于,每一个Map存储的Entry数量变少了,线程越多键值对越多。如今的键值对的数量是由ThreadLocal的数量决定的,通常状况下ThreadLocal的数量少于线程的数量,并且并非每一个线程都须要建立ThreadLocal变量。当Thread销毁时,ThreadLocal也会随之销毁,减小了内存的使用,以前的方案中线程销毁后,ThreadLocalMap仍然存在。
首先获取线程,而后获取线程的Map。若是Map不为空则将当前ThreadLocal的引用做为key设置到Map中。若是Map为空,则建立一个Map并设置初始值。
首先获取当前线程,而后获取Map。若是Map不为空,则Map根据ThreadLocal的引用来获取Entry,若是Entry不为空,则获取到value值,返回。若是Map为空或者Entry为空,则初始化并获取初始值value,而后用ThreadLocal引用和value做为key和value建立一个新的Map。
删除当前线程中保存的ThreadLocal对应的实体entry。
该方法的第一次调用发生在当线程经过get方法访问线程的ThreadLocal值时。除非线程先调用了set方法,在这种状况下,initialValue才不会被这个线程调用。每一个线程最多调用依次这个方法。
该方法只返回一个null,若是想要线程变量有初始值须要经过子类继承ThreadLocal的方式去重写此方法,一般能够经过匿名内部类的方式实现。这个方法是protected修饰的,是为了让子类覆盖而设计的。
ThreadLocalMap是ThreadLocal的静态内部类,没有实现Map接口,独立实现了Map的功能,内部的Entry也是独立实现的。
与HashMap相似,初始容量默认是16,初始容量必须是2的整数幂。经过Entry类的数据table存放数据。size是存放的数量,threshold是扩容阈值。
Entry继承自WeakReference,key是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
内存溢出:没有足够的内存供申请者提供
内存泄漏:程序中已动态分配的堆内存因为某种缘由程序未释放或没法释放,形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃等验证后沟。内存泄漏的堆积会致使内存溢出。
弱引用:垃圾回收器一旦发现了弱引用的对象,无论内存是否足够,都会回收它的内存。
内存泄漏的根源是ThreadLocalMap和Thread的生命周期是同样长的。
若是在ThreadLocalMap的key使用强引用仍是没法彻底避免内存泄漏,ThreadLocal使用完后,ThreadLocal Reference被回收,可是Map的Entry强引用了ThreadLocal,ThreadLocal就没法被回收,由于强引用链的存在,Entry没法被回收,最后会内存泄漏。
在实际状况中,ThreadLocalMap中使用的key为ThreadLocal的弱引用,value是强引用。若是ThreadLocal没有被外部强引用的话,在垃圾回收的时候,key会被清理,value不会。这样ThreadLocalMap就出现了为null的Entry。若是不作任何措施,value永远不会被GC回收,就会产生内存泄漏。
ThreadLocalMap中考虑到这个状况,在set、get、remove操做后,会清理掉key为null的记录(将value也置为null)。使用完ThreadLocal后最后手动调用remove方法(删除Entry)。
也就是说,使用完ThreadLocal后,线程仍然运行,若是忘记调用remove方法,弱引用比强引用能够多一层保障,弱引用的ThreadLocal会被回收,对应的value会在下一次ThreadLocalMap调用get、set、remove方法的时候被清除,从而避免了内存泄漏。
构造函数建立一个长队为16的Entry数组,而后计算firstKey的索引,存储到table中,设置size和threshold。
firstKey.threadLocalHashCode & (INITIAL_CAPACITY-1)用来计算索引,nextHashCode是Atomicinteger类型的,Atomicinteger类是提供原子操做的Integer类,经过线程安全的方式来加减,适合高并发使用。
每次在当前值上加上一个HASH_INCREMENT值,这个值和斐波拉契数列有关,主要目的是为了让哈希码能够均匀的分布在2的n次方的数组里,从而尽可能的避免冲突。
当size为2的幂次的时候,hashCode & (size - 1)至关于取模运算hashCode % size,位运算比取模更高效一些。为了使用这种取模运算, 全部size必须是2的幂次。这样一来,在保证索引不越界的状况下,减小冲突的次数。
ThreadLocalMao使用了线性探测法来解决冲突。线性探测法探测下一个地址,找到空的地址则插入,若整个空间都没有空余地址,则产生溢出。例如:长度为8的数组中,当前key的hash值是6,6的位置已经被占用了,则hash值加一,寻找7的位置,7的位置也被占用了,回到0的位置。直到能够插入为止,能够将这个数组当作一个环形数组。