java之ThreadLocal
前言
多线程可以充分利用cpu的同时,也带来了很多问题,比如说共享变量的问题,有很多情况下,多个线程想持有自己的局部变量,或者说是某些变量需要与线程所关联,java提供了一个类叫做ThreadLoacl
那么这个类有哪些作用呢?具体在哪里提现出来了呢?
比如spring的获取数据库连接的时候,spring中的很多bean都是单例的,是非状态的,而数据库连接是一种有状态的对象,所以spring一定在创建出connection之后在threadlocal中保存了它
其中有一处的代码是这样写的:
TransactionSynchronizationManager这个类中
代码很简单,以dataSource为key,ConnectionHolder为value存进了一个map里,而这个叫做resources的map是一个Threadlocal变量,存在于当前线程的ThreadlocalMap里。
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
调用到它的地方为
AbstractPlatformTransactionManager spring的一个模板类里面的dobegin抽象方法,各个实现类分别有不同的实现,我拿的是jbdc数据库连接的代码
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 设置数据库事务的隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
// 事务设置成自动提交的
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
// 如果持有的事务是新的数据库连接的话,就将其绑定到threadlocal里面
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
想必看到这里是不是还是有点不过瘾,比如说我开启了一个事务,具体的闭环是怎么实现的,从获取数据库连接,到绑定。一路上的流程。我也写一下
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// 这一步就会从ThreadLoacl里面去获取数据库连接
Object transaction = doGetTransaction();
.............. 代码省略
// 如果存在连接的话则直接return,不存在则走下面的绑定连接
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// 这个代码也很关键 里面有对spring 传播性事务的具体处理
return handleExistingTransaction(definition, transaction, debugEnabled);
}
..................代码省略
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 绑定连接
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
看了上面的代码,大概也知道ThreadLocal 是怎么用的了对吧。然后我再来大致的讲讲ThreadLocal。
直接看起源码
public T get() {
Thread t = Thread.currentThread();
//从线程里面获取一个ThreadLocalMap,ThreadLocalMap 是ThreadLocal的一个内部静态类,从这里大概我们可以感知到Thread 这个类里面大概是持有了一个ThreadLocal.ThreadLocalMap 的一个引用,也确实是的,代码见后面
ThreadLocalMap map = getMap(t);
if (map != null) {
// ThreadLocalMap 维护了一个Entry结构 这个结构继承了WeakReference
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果不为空就取值,为空就进行初始化把null塞进去
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
讲一下上面的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
// 获取hash值,与table的长度-1 进行一个与操作,定位索引下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果不为空,并且引用也为ThreadLocal的对象的话 就返回
if (e != null && e.get() == key)
return e;
else
//
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 如果entry并不为空,说明存的threadlocal引用 和传进来的key不一致时,进行自循操作,如果相等还是直接返回值,这里也是java防止内存溢出的一个操作了
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
//如果已经为空了。从当前位置开始,往后再找一段,碰到脏entry进行清理,碰到null结束,因为entry是弱引用的
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
我们再来看看set方法
public void set(T value) {
Thread t = Thread.currentThread();
// 非常简单的获取Thread里面的ThreadLocalMap引用,
如果不为空,值进行覆盖,如果为空则创建,并讲值放进去
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
再讲讲remove方法吧,毕竟ThreadLocal 使用的最常见的也就 set,get,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)]) {
// 只要有等于的,就将引用置为null,并且执行脏数据清除方法
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal出现OOM内存溢出的场景和原理分析
确实讲到了ThreadLocal,必然要讲讲OOM。
弱引用
java语言中为对象的引用分为了四个级别,分别为 强引用 、软引用、弱引用、虚引用。
其余三种具体可自行查阅相关资料。
弱引用具体指的是java.lang.ref.WeakReference类。
对对象进行弱引用不会影响垃圾回收器回收该对象,即如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否)。
再来说说内存泄漏,假如一个短生命周期的对象被一个长生命周期对象长期持有引用,将会导致该短生命周期对象使用完之后得不到释放,从而导致内存泄漏。
因此,弱引用的作用就体现出来了,可以使用弱引用来引用短生命周期对象,这样不会对垃圾回收器回收它造成影响,从而防止内存泄漏。
ThreadLocal中的弱引用
1.为什么ThreadLocalMap使用弱引用存储ThreadLocal?
假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏。
因此,使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏。
2.那通常我们说的Threadlocal的内存泄漏是怎么来的呢?
我们注意到Entry对象中,虽然Key(ThreadLocal)是通过弱引用引入的,但是value即变量值本身是通过强引用引入。
这就导致,假如不作任何处理,由于ThreadLocalMap和线程的生命周期是一致的,当线程资源长期不释放(线程池),如果想复现oom的情况,请开一个线程池,并且使用ThreadLocal,不去执行remove()方法,即使ThreadLocal本身由于弱引用机制已经回收掉了,但value还是驻留在线程的ThreadLocalMap的Entry中。即存在key为null,但value却有值的无效Entry。导致内存泄漏。
为什么key不使用强引用呢?
就像上面讲的如果一个长生命周期持有短生命周期的强引用,就会导致内存泄漏。如果key是强引用的,使用Threadlocal的对象已经回收了,但是由于线程还持有着ThreadLocalMap,而Threadlocalmap又持有着ThreadLocal的强引用,就会导致ThreadLocal无法回收,一样会导致内存泄漏问题