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无法回收,一样会导致内存泄漏问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值