赞
踩
ThreadLocal会为每个线程创建单独的变量副本,避免因多线程操作共享变量导致数据不一致的现象。
看一个简单的数据库处理类,逻辑很简单一个是打开连接,一个是关闭连接。
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if (connect == null) {
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if (connect != null)
connect.close();
}
}
我们回顾一下多线程下操作共享变量安全性的保证:
互斥同步:synchroniezd 和 reentrantLock
非阻塞同步:CAS、AtomicXXX
无同步方案:栈封闭、本地存储(Thread Local)。
对于上面的代码,因为有共享变量,多线程下可能会有安全问题。
安全问题:一个线程openConnection的同时,另一个变量在closeConnection;
同步问题:不互斥,有多个线程同时进入openConnection方法,多次创建了connect。
思考一个问题:需不需要互斥同步。
假设每个线程都有自己的connect变量,各个线程线程之间拿到connect之后,相互之间是没有依赖的。即一个线程修改了这个connect,不会影响其他线程对这个connect的使用。
因为每个线程使用的是对应CPU下的缓存,而不是内存中的数据。
但这会导致服务器压力过大,因为会在方法中频繁的创建数据连接。
ThreadLocal会在每个线程中对该变量创建一个副本,即每个线程都会有这样一个副本,互不影响。这样就不会造成线程安全和性能问题。
常用的例子
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("", "", "");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
public Connection getConnection() {
return dbConnectionLocal.get();
}
}
注意下ThreadLocal的修饰符
如果我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来,那么通常在这个类中定义private static类型的ThreadLocal 实例。
同时,threadLocal副本会造成内存的占用。
看下列代码:使用线程池来操作ThreadLocal。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo {
static class LocalVariable {
private Long[] a = new Long[1024 * 1024];
}
// (1)
final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
// (2)
final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();
public static void main(String[] args) throws InterruptedException {
// (3)
Thread.sleep(5000 * 4);
for (int i = 0; i < 50; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
// (4)
localVariable.set(new LocalVariable());
// (5)
System.out.println("use local varaible" + localVariable.get());
localVariable.remove();
}
});
}
// (6)
System.out.println("pool execute over");
}
}
如果使用线程池操作ThreadLocal会造成内存泄漏。
内存泄漏:指一个对象不使用了,但是一直占据着内存,具体的:对象不使用了,但是还被其他对象引用着,就会导致GC时不能回收此对象(根的可达性分析),这种情况就是内存泄漏。
因为对于线程池中没有销毁的线程,里面总是存在着<ThreadLocal, LocalVariable> 的强引用,而对于ThreadLocalMap 对于key虽然是弱引用,但是强引用不释放,弱引用就会一直有值,同时创建的Local Variable对象也不会释放,就造成了内存泄漏。
具体地,泄漏的内存 = coreThreadNum * LocalVariable对象的大小
ThreadLocal提供了一个清除线程中对象的方法, 即 remove。来避免内存泄漏的情况。
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
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;
}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class DateUtils {
public static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
}
调用
DateUtils.df.get().format(new Date());
当我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来时,使用ThreadLocal。
为每一个线程维护一个序列号。
public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};
public static int get() {
return ((Integer) (serialNum.get())).intValue();
}
}
看一下set和get
先获取当前线程的map,然后set值,进行更新。如果这个线程没有map就先创建这个线程的map,并set值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
获取当前线程的Map,如果不为空,则获取这个线程对应的值,否则初始化。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap threadLocals = getMap(t);
if (threadLocals != null) {
ThreadLocalMap.Entry e = threadLocals.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
***分析了get和set方法,对于ThreadLocal的线程隔离的实现其实很简单,就是为每个线程创建一个map,其中key存储着线程本身,value就是操作的共享变量。 ***
每个线程在get时,如果此线程没有set,则获取的是null。
参考:
https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。