作用和原理
ThreadLocal就是为每一个使用该变量的线程都提供了一个变量值的副本。是Java中一种较为特殊的线程绑定而已,是每一个线程都可以独立的改变自己的副本,而不会跟其他线程的副本发生冲突。
从线程的角度来看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且ThreadLocal实例是可以访问的;在线程结束后,其线程的局部实例的所有副本都会被垃圾回收,除非存在对这些副本的引用。
ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM为每个运行的线程绑定了私有的本地变量的存取空间,从而为多线程环境常出现的额并发访问问题提供了一种隔离机制。
实现:
ThreadLocal中有一个map,用于存储每一个线程的变量的副本。
也就是说,对于多线程资源共享问题,同步机制采用了“以时间换空间的方式”,而ThreadLocal采用了“以空间换时间”的方式,前者仅提供一份变量,让不同的变量排队访问。而后者为每一个线程提供了一个副本,因此可以同时访问互不影响。
方法
void set(obj) : 设置当前线程的线程局部变量的值。
public Object get() : 返回房钱线程对应的线程局部变量。
public void remove() : 删除当前线程局部变量的值,减少内存的占用。
线程执行结束之后也会回收对应线程占用的内存空间,所以显示的调用该方法不是必须操作,只是为了加快内存的回收速度。
protected Object initalValue() : 返回该线程局部变量的初始值。该方法是一个延迟调用方法,在县城第一次调用get,set时才执行,并且仅执行一次。
ThreadLocal中的缺省实现直接返回一个null.
package com.cyan.ThreadLocalDamo;
import java.util.Random;
/**
* @description: 在对象中,获取对象的方法中,先从ThreadLocal中去获取对象,如果对象为空,则创建对象并放进去。
* 保证了在线程中获取对象的时候,将创建的对象跟放入到ThreadLocal中,以跟当前线程进行绑定。
* 从而保证在此线程中其他位置通过ThreadLocal获取的对象是同一个对象。
*/
public class ThreadLocalDamo2 {
static ThreadLocal<People> threadLocal = new ThreadLocal<>();
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();
People people = People.getInstance(); // 第一次执行此处代码会创建新的对象并放入ThreadLocal,使对象和当前线程绑定。
people.setName("AAA" + data);
people.setAge(data);
System.out.println(Thread.currentThread().getName() + " Name: " + people.getName() + " Age: " + people.getAge());
new A().get(); //此处获取的对象都是从ThreadLocal中获取对象,所以获取到的是跟当前线程绑定的对象副本
new B().get();
}
}).start();
}
}
static class A {
public void get() {
System.out.println("A: " + Thread.currentThread().getName() + " Name: " + People.getInstance().getName() + " Age: " + People.getInstance().getAge());
}
}
static class B {
public void get() {
System.out.println("B: " + Thread.currentThread().getName() + " Name: " + People.getInstance().getName() + " Age: " + People.getInstance().getAge());
}
}
static class People {
private People() {
}
public static People getInstance() {
//从ThreadLocal中获取对象
People people = threadLocal.get();
if (people==null) {
people = new People();
threadLocal.set(people);
}
return people;
}
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
模拟实现
ThreadLocal类中有一个map,用于存储每个线程的变量副本,map中元素的键是线程对象,而值对应线程的变量副本。
package com.cyan.ThreadLocalDamo;
/**
* @description: initialValue()会在某个线程第一次执行get或set的时候调用一次。
*/
public class ThreadLocalDemo3 {
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
//此处重写initialValue方法指定每个线程的局部变量的初始值
public Integer initialValue() {
return 0;
}
};
public int getNextNum() {
//线程调用并将当前线程的局部变量进行改变。改变的只有当前线程的局部变量。
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
ThreadLocalDemo3 demo = new ThreadLocalDemo3();
TestClient t1 = new TestClient(demo);
TestClient t2 = new TestClient(demo);
TestClient t3 = new TestClient(demo);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private ThreadLocalDemo3 demmo;
public TestClient(ThreadLocalDemo3 demmo) {
this.demmo = demmo;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("Thread : " + Thread.currentThread().getName() +
" --> Num : " + demmo.getNextNum());
}
}
}
}
以上例子,每个线程产生了各自单独的序列号,虽然共享一个ThreadLocal实例,但是并没有发生任何冲突,这是因为ThreadLocal为每个线程提供了一个单独的副本。
Thread的同步机制的比较
相同点
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突的问题
不同点
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这是该变量是多个线程共享的,使用同步机制要求程序准确的分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放锁等问题。
ThreadLocal会为每个线程提供独立的变量副本,从而隔离多个线程对数据的访问冲突。因为每个线程都有自己的变量副本,所以不需要进行同步。
ThreadLocal可以支持任何类型的对象。在jdk5后加入优化了泛型。
高级货
要做到多个Dao共享同一Conn,必须在共同的外部类使用ThreadLocal保存Conn。
Connection的管理
package com.cyan.ThreadLocalDamo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @description:
*/
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test",
"username",
"password"
);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
};
public static Connection getConn() {
return connectionHolder.get();
}
public static void setConn(Connection conn) {
connectionHolder.set(conn);
}
}
Session的管理
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义SessionFactory
static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 获取当前线程中的Session
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
总结
ThreadLocal主要用于结局多线程中数据因并发产生不一致的问题。ThreadLocal为每个线程中并发访问的数据提供了一个副本,通过访问副本来运行业务,这样的结果就是耗费了内存,但是减少了线程同步带来的性能消耗,也减少了并发控制的复杂度。
ThreadLocal不能使用原子类型。??????
ThreadLocal和Synchornized都用于解决多线程并发访问。synchornized是利用锁的机制,确保使变量或代码块在某一时刻只能被一个线程访问,而ThreadLocal为每个线程都提供了变量的副本,使得献策还能够在某一个时刻访问到的不是同一个对象,这样就隔离了多个线程对数据的数据共享。synchornized主要用于多个线程间通信时能够获得共享数据。
synchornized主要用于线程间的数据共享,ThreadLocal主要用于线程间的数据隔离。