ThreadLocal的缺点

ly_cyclone 2014-12-10 08:00:14
JAVA并发实战这本书上说ThreadLocal类似于全局变量,它能降低代码的可重用性。请问这是为什么?为什么会降低代码的可重用性呢?请大神写出代码举个例子好吗
...全文
1055 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhangyu84848245 2015-12-06
  • 打赏
  • 举报
回复
这是Lucene 源代码中的一句注释你可以参考下 全部的代码如下

package org.apache.lucene.util;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.Closeable;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Java's builtin ThreadLocal has a serious flaw: it can take an arbitrarily
 * long amount of time to dereference the things you had stored in it, even once
 * the ThreadLocal instance itself is no longer referenced. This is because
 * there is single, master map stored for each thread, which all ThreadLocals
 * share, and that master map only periodically purges "stale" entries.
 *
 * While not technically a memory leak, because eventually the memory will be
 * reclaimed, it can take a long time and you can easily hit OutOfMemoryError
 * because from the GC's standpoint the stale entries are not reclaimable.
 * 
 * This class works around that, by only enrolling WeakReference values into the
 * ThreadLocal, and separately holding a hard reference to each stored value.
 * When you call {@link #close}, these hard references are cleared and then GC
 * is freely able to reclaim space by objects stored in it.
 *
 * We can not rely on {@link ThreadLocal#remove()} as it only removes the value
 * for the caller thread, whereas {@link #close} takes care of all threads. You
 * should not call {@link #close} until all threads are done using the instance.
 *
 * @lucene.internal
 */

public class CloseableThreadLocal<T> implements Closeable {

	private ThreadLocal<WeakReference<T>> t = new ThreadLocal<>();

	// Use a WeakHashMap so that if a Thread exits and is GC'able, its entry may be removed:
	private Map<Thread, T> hardRefs = new WeakHashMap<>();

	// Increase this to decrease frequency of purging in get:
	private static int PURGE_MULTIPLIER = 20;

	// On each get or set we decrement this; when it hits 0 we
	// purge. After purge, we set this to
	// PURGE_MULTIPLIER * stillAliveCount. This keeps
	// amortized cost of purging linear.
	private final AtomicInteger countUntilPurge = new AtomicInteger(PURGE_MULTIPLIER);
			

	protected T initialValue() {
		return null;
	}

	public T get() {
		WeakReference<T> weakRef = t.get();
		if (weakRef == null) {
			T iv = initialValue();
			if (iv != null) {
				set(iv);
				return iv;
			} else {
				return null;
			}
		} else {
			maybePurge();
			return weakRef.get();
		}
	}

	public void set(T object) {

		t.set(new WeakReference<>(object));

		synchronized (hardRefs) {
			hardRefs.put(Thread.currentThread(), object);
			maybePurge();
		}
	}

	private void maybePurge() {
		if (countUntilPurge.getAndDecrement() == 0) {
			purge();
		}
	}

	// Purge dead threads
	private void purge() {
		synchronized (hardRefs) {
			int stillAliveCount = 0;
			for (Iterator<Thread> it = hardRefs.keySet().iterator(); it
					.hasNext();) {
				final Thread t = it.next();
				if (!t.isAlive()) {
					it.remove();
				} else {
					stillAliveCount++;
				}
			}
			int nextCount = (1 + stillAliveCount) * PURGE_MULTIPLIER;
			if (nextCount <= 0) {
				// defensive: int overflow!
				nextCount = 1000000;
			}

			countUntilPurge.set(nextCount);
		}
	}

	@Override
	public void close() {
		// Clear the hard refs; then, the only remaining refs to
		// all values we were storing are weak (unless somewhere
		// else is still using them) and so GC may reclaim them:
		hardRefs = null;
		// Take care of the current thread right now; others will be
		// taken care of via the WeakReferences.
		if (t != null) {
			t.remove();
		}
		t = null;
	}
}

zhangyu84848245 2015-12-06
  • 打赏
  • 举报
回复
/** * Java's builtin ThreadLocal has a serious flaw: it can take an arbitrarily * long amount of time to dereference the things you had stored in it, even once * the ThreadLocal instance itself is no longer referenced. This is because * there is single, master map stored for each thread, which all ThreadLocals * share, and that master map only periodically purges "stale" entries. * * While not technically a memory leak, because eventually the memory will be * reclaimed, it can take a long time and you can easily hit OutOfMemoryError * because from the GC's standpoint the stale entries are not reclaimable. * * This class works around that, by only enrolling WeakReference values into the * ThreadLocal, and separately holding a hard reference to each stored value. * When you call {@link #close}, these hard references are cleared and then GC * is freely able to reclaim space by objects stored in it. * * We can not rely on {@link ThreadLocal#remove()} as it only removes the value * for the caller thread, whereas {@link #close} takes care of all threads. You * should not call {@link #close} until all threads are done using the instance. * * @lucene.internal */
guohao_11 2014-12-18
  • 打赏
  • 举报
回复
我理解,全局变量指的是这个线程处理的整个业务逻辑的全局。例如web中,一次请求就是一个线程处理,这个里面就可以设置一个全局变量,用于避免参数传递。减低重用性,我理解是因为代码依赖了一个全局的变量,当然不容易复用在其他业务场景了。
humanity 2014-12-18
  • 打赏
  • 举报
回复
改了一下演示的代码:

public class Context {
           static final ThreadLocal contexts = new ThreadLocal();
            public static Context getCurrentContext() { return contexts.get(); }
            protected static void init() { contexts.set(new Context()); }           
            protected static void destroy() { contexts.set(null);}
            private Context() { };
            // 其它框架级别的业务方法。
}
...   doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
             Context.init();

       try {
                   chain.doFilter(request, response);
                    // 在这个 context 边界界定的范围内我们的其它的 API 都可以 Context.getCurrentContext() 来得到它的 Context 对象,而不会在线程之间出现共用和线程安全问题。
        } finally {
                 Context.destroy();
         }
            
}
引用 9 楼 humanity 的回复:
对啊,主要是设计时要想清楚你需要它做什么,它更适合做什么,我们不要把它用在它最不适合的场景中,没有什么是万能的。这个类被设计出来是为了解决特定的问题的,不是解决所有问题的。首先我们要了解它的设计,然后根据它的约定来使用它。 在线程池中也是一样的,比如,一个 servlet request (我们都知道它背后肯定是一个线程池的 worker 线程): 下面这段代码有语法错误,主要是演示用法,不管是不是在线程池中我们都可以有一个 Context,同时也要对它的生命周期规律了解彻底(比如 servlet request 是个什么生命周期,它结合 servlet filter 的工作过程是什么样的),找到我们的 Context 要起作用的 boundary 边界在哪里,这个边界没控制精确就导致各种错误或内存消耗过大或事务关联搞错了等:
humanity 2014-12-18
  • 打赏
  • 举报
回复
对啊,主要是设计时要想清楚你需要它做什么,它更适合做什么,我们不要把它用在它最不适合的场景中,没有什么是万能的。这个类被设计出来是为了解决特定的问题的,不是解决所有问题的。首先我们要了解它的设计,然后根据它的约定来使用它。 在线程池中也是一样的,比如,一个 servlet request (我们都知道它背后肯定是一个线程池的 worker 线程): 下面这段代码有语法错误,主要是演示用法,不管是不是在线程池中我们都可以有一个 Context,同时也要对它的生命周期规律了解彻底(比如 servlet request 是个什么生命周期,它结合 servlet filter 的工作过程是什么样的),找到我们的 Context 要起作用的 boundary 边界在哪里,这个边界没控制精确就导致各种错误或内存消耗过大或事务关联搞错了等:

... static final ThreadLocal sessions = new ThreadLocal();
...   doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
          Session context = new Session();
            sessions.set(context);

       try {
                   chain.doFilter(request, response);
        } finally {
                 session.close();
                  sesssions.set(null);
         }
            
}
引用 8 楼 skgary 的回复:
[quote=引用 7 楼 humanity 的回复:] [quote=引用 6 楼 skgary 的回复:] 几个严重的问题: 1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。 2. 代码的耦合度高,且测试不易。
第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。 参考前面。 [/quote] 第一个绝对是问题,不用线程池无所谓,但用线程池的话,必须小心。实在不行,只能在线程程外面来清理。[/quote]
skgary 2014-12-15
  • 打赏
  • 举报
回复
引用 7 楼 humanity 的回复:
[quote=引用 6 楼 skgary 的回复:] 几个严重的问题: 1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。 2. 代码的耦合度高,且测试不易。
第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。 参考前面。 [/quote] 第一个绝对是问题,不用线程池无所谓,但用线程池的话,必须小心。实在不行,只能在线程程外面来清理。
humanity 2014-12-13
  • 打赏
  • 举报
回复
引用 6 楼 skgary 的回复:
几个严重的问题: 1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。 2. 代码的耦合度高,且测试不易。
第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。 参考前面。
skgary 2014-12-11
  • 打赏
  • 举报
回复
几个严重的问题: 1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。 2. 代码的耦合度高,且测试不易。
scott_129 2014-12-10
  • 打赏
  • 举报
回复
好像是这么回事
scott_129 2014-12-10
  • 打赏
  • 举报
回复
好像是这么回事
scott_129 2014-12-10
  • 打赏
  • 举报
回复
好像是这么回事
AceShot 2014-12-10
  • 打赏
  • 举报
回复
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。这种独立的拷贝与代码重用是违背的,所以说会降低代码的可重用性。但它不是类似全局变量,相反是类似局部变量。
AceShot 2014-12-10
  • 打赏
  • 举报
回复
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。这种独立的拷贝与代码重用是违背的,所以说会降低代码的可重用性。但它不是类似全局变量,相反是类似局部变量。

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧