ThreadLocal
1.是什么。
ThreadLocal是一个线程局部变量的类
ThrealLocal 在解决非同步锁synchronized状态下的线程安全有重要意义的。(性能)
我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。
我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。
虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。
按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但Spring的DAO模板类并未采用线程同步机制,因为线程同步限制了并发访问,会带来很大的性能损失。
此外,通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现难度。那模板类究竟仰丈何种魔法神功,可以在无需同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal!
ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒。
其他例子:
ThreadLocal ~ Map 用来保存线程局部变量
2. 做什么,
为解决线程同步问题提供了一种新的思路
3.怎么用:
需要注意到一点是:ThreadLocal保存的临界资源类型。像Connection这种。需要的资源,线程完了,不用了,就释放掉。不是一个修改型的临界资源。
例如,银行取钱,
用到了临界资源Session
就是这个例子很好理解。两个线程不能同时使用一个Connection,此时,解决的办法有两个,
1.使用synchronized,同步锁,把connection锁住,一个线程使用完后,其他的线程才能使用。
2.使用ThreadLocal,每个线程拥有与此connection“想等的局部对象”是通过new出来的。(即,如果线程局部变量没有这个对象,就new一个出来)
-
public class ThreadLocal<T>
- extends Object
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如,以下类生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。
import java.util.concurrent.atomic.AtomicInteger; public class UniqueThreadIdGenerator { // 这个是要保存的局部变量,像Connection private static final AtomicInteger uniqueId = new AtomicInteger(0); private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return uniqueId.getAndIncrement(); } }; public static int getCurrentThreadId() { return uniqueId.get(); } }
其中要注意重写那一段的代码,以前很少这样,在new一个实例对象的时候修改了ThreadLocal(或者其他类的方法)里面的方法。那个
在ThreadLocal中。initialValue()方法是被定义为 protected 的,明显是为了让子类重写的。
protected T initialValue() { return null; }下面这个是Hibernate中一个典型的ThreadLocal应用。
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }3.怎么用
我现在已经知道了ThreadLocal是用来解决什么问题的。(线程同步的另一种思路)通过new 新的临界资源副本,保存到相应线程中。供该线程使用。
当我了解以上需求时,我考虑该在什么地方。。怎么样合理地来使用ThreadLocal(可以理解为一个map),
1.临界资源,同步资源等问题,发生在两种地方,
a.同线程的不同模块(对同一资源的操作)
b.不同线程的同一模块(........)
c.不同线程的不同模块(........)
先前,我们解决这种问题的首先想到的就是同步锁,对操作临界资源的代码块加锁(嵌套锁)。但是这很影响效率,
ThreadLocal就是另一种思路的产物,及线程局部变量(实际是new一个)。注意上面的a,b,c三种情况下,我们就需要临界资源的副本。以免不同线程/模块,操作统一临界资源,发生错误。
即在
a.同线程的不同模块,我们需要线程局部变量
b.不同线程的同一模块,我们需要......
c.不同线程的不同模块,我们需要......
归纳来看,我们需要临界资源的地方,都要取副本,不能直接使用本体
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; public class ThreadLocalTest { // private static ThreadLocal<Integer> dataMap = new ThreadLocal<Integer>(); private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>(); public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put data :" + data); // dataMap.set(data); // 这里是一个基本类型变量, // 可以放一个对象。包含很多数据的对象,或者集合 /* * MyThreadScopeData myData = new MyThreadScopeData(); * myData.setName("name" + data); myData.setAge(data); * map.set(data); */ MyThreadScopeData.getThreadInstance() .setName("name" + data);// 本线程相关的对象, MyThreadScopeData.getThreadInstance().setAge(data); new A().get(); new B().get(); } }).start(); } } static class A { public void get() { // int data = dataMap.get(); MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("A from" + Thread.currentThread().getName() + " get Mydata :" + myData.getName() + " " + myData.getAge()); } } static class B { public void get() { // int data = dataMap.get(); MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("B from" + Thread.currentThread().getName() + " get Mydata :" + myData.getName() + " " + myData.getAge()); } } } /** * 这是一个封装好了要用的线程局部变量的类。 * 使用到了ThreadLocal,当在线程中调用这个类的getInstance()方法,能返回临界资源的“副本”,所以这个类的名字应该明确 * ,返回什么副本对象,名字就应该是什么。 例如,是Connection,则我把它命名为ThreadScopeConnection.getInstance() * 就返回一个线程范围内的Connection * * @author jake * */ class ThreadScopeConnection { private static Connection conn = null; private static ThreadLocal<Connection> map = new ThreadLocal<Connection>(); private ThreadScopeConnection() { } public static Connection getThreadScopeInstance() { Connection instance = map.get(); // 这里只是为了让getConnection不报错 String url = null; String user = null; String password = null; if (instance == null) { try { instance = DriverManager.getConnection(url, user, password); } catch (SQLException e) { e.printStackTrace(); } map.set(instance); } return instance; } } /** * 这个例子就不够明确,返回的是一个data?什么数据,不知道。 * 当然它是正确的,但是它不好,它把两个对象封装到一个了,原本应该是--一个数据对象,和一个线程范围局部变量对象 * * * @author jake * */ class MyThreadScopeData { // 临界资源的副本.这里资源不一定是MyThreadScopeData这个类,也可以是Session,Connection这种对象,更好理解。 // private static MyThreadScopeData instance = new MyThreadScopeData(); private static MyThreadScopeData instance = null; private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>(); private MyThreadScopeData() { } // 重构快捷键 Shift + Alt + R /** * 以下是ThreadLocal的get()方法 public T get() { Thread t = * Thread.currentThread(); //以当前线程为key取存放在map中的数据对象。 ThreadLocalMap map = * getMap(t); if (map != null) { ThreadLocalMap.Entry e = * map.getEntry(this); if (e != null) return (T)e.value; } return * setInitialValue(); } */ public static MyThreadScopeData getThreadInstance() { MyThreadScopeData instance = map.get(); if (instance == null) { instance = new MyThreadScopeData(); map.set(instance); } return instance; } private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }以上是摘出自黑马训练营的一段代码,有点不好的地方,而且老师也没有很明白地讲明。自己小结了下。
另外:Iteye的这篇博文还是对我很有帮助的。
http://www.iteye.com/topic/103804
多追究下去不是我本意,本篇学习日记就到这,以后再实际项目中希望能慢慢深入