java ThreadLocal
来源:互联网 发布:怎么作曲编曲软件 编辑:程序博客网 时间:2024/06/05 08:15
ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复,每一个“线程对象”对应线程的“变量副本”,而到达了线程安全。
我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。
我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。
虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。
按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但Spring的DAO模板类并未采用线程同步机制,因为线程同步限制了并发访问,会带来很大的性能损失。
此外,通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现难度。那模板类究竟仰丈何种魔法神功,可以在无需同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal!
ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒。
ThreadLocal是什么
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法:
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
· void set(Object value)
设置当前线程的线程局部变量的值。
· public Object get()
该方法返回当前线程所对应的线程局部变量。
· public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
· protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
代码如下:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
public class SequenceNumber { /**1.通过匿名内部类覆盖ThreadLocal 的initialValue() 方法 指定初始值 */ private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ public Integer initialValue() { return 0; } }; /**2. 获取下一个序列值*/ public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args) { SequenceNumber sn = new SequenceNumber(); /**3. 3个线程共享sn 各自产生序列号 */ TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); t1.start(); t2.start(); t3.start(); } private static class TestClient extends Thread { private SequenceNumber sn; public TestClient(SequenceNumber sn) { this.sn = sn; } public void run() { /**4. 每个线程打出 3个序列值 */ for (int i = 0; i < 3; i++) { System.out.println("thread[" + Thread.currentThread().getName() + "]sn[" +sn.getNextNum() +"]"); } } }}通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子在1处所示。TestClient线程产生一组序列号, 在3处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:thread[Thread-1]sn[1]thread[Thread-2]sn[1]thread[Thread-0]sn[1]thread[Thread-2]sn[2]thread[Thread-1]sn[2]thread[Thread-2]sn[3]thread[Thread-0]sn[2]thread[Thread-1]sn[3]考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
ThreadLocal 不是用来解决共享对象的多线程访问问题的 线程中的对象是该线程自己使用的对象 其他线程是不需要访问的,也访问不到的 各个线程中访问的是不同的对象
另外:
以上面的为基础来想,如果说其他线程依然能够访问,那岂不是依旧有并发问题?答案是肯定的。 (当然,也可以这么做,但是并发问题就需要另行解决) 所以正确的使用情况下,不应该被自己以外的线程去使用,为什么呢? (1)每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 (2)将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦
其实,关键点在于:
ThreadLocal类实现了“为每个线程提供不同的变量拷贝”
那么重点来了:
ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。值得注意的就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是我们要保存的对象。获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,set()方法的代码是对应。
- java ThreadLocal
- java threadlocal
- java threadlocal
- Java ThreadLocal
- Java ThreadLocal
- Java ThreadLocal
- JAVA ThreadLocal
- java threadlocal
- Java ThreadLocal
- JAVA ThreadLocal
- [Java]ThreadLocal
- java ThreadLocal
- Java ThreadLocal
- Java ThreadLocal
- Java ThreadLocal
- java ThreadLocal
- Java ThreadLocal
- Java ThreadLocal
- spring 占位符 得配置方式
- 用指针计算数组元素的和
- 【Hadoop】Flume官方文档翻译——Flume 1.7.0 User Guide (unreleased version)中一些知识点
- Spring 4 使用Freemarker模板发送邮件&添加附件
- 单片机中进制转换知识
- java ThreadLocal
- 如何利用github创建博客
- Map,Filter和Reduce
- Swift基础之两种选择星星的评价样式并获取星星的索引值
- java中io读写时流的关闭注意,代码查错
- Android Support Library的前世今生
- 淘宝的页面是如何生成的?
- js对象
- zk follower原来也可以写数据呀