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
多追究下去不是我本意,本篇学习日记就到这,以后再实际项目中希望能慢慢深入