jjzjj

java - 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?

coder 2023-05-14 原文

我对 ThreadLocal 的使用

在我的 Java 类中,我有时会使用 ThreadLocal主要是为了避免不必要的对象创建:

@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {

    private final Date then;

    public DateSensitiveThing(Date then) {
        this.then = then;
    }

    private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>()   {
        @Override
        protected Calendar initialValue() {
            return new GregorianCalendar();
        }
    };

    public Date doCalc(int n) {
        Calendar c = threadCal.get();
        c.setTime(this.then):
        // use n to mutate c
        return c.getTime();
    }
}

我这样做是有正当理由的 - GregorianCalendar是那些有状态的、可变的、非线程安全的对象之一,它提供跨多个调用的服务,而不是表示一个值。此外,实例化被认为是“昂贵的”(这是否属实不是这个问题的重点)。 (总的来说,我真的很佩服它:-))

Tomcat 如何提示

但是,如果我在任何汇集线程的环境中使用这样的类 - 并且我的应用程序无法控制这些线程的生命周期 - 那么就有可能发生内存泄漏。 Servlet 环境就是一个很好的例子。

事实上,当 web 应用程序停止时,Tomcat 7 会发出这样的提示:

SEVERE: The web application [] created a ThreadLocal with key of type [org.apache.xmlbeans.impl.store.CharUtil$1] (value [org.apache.xmlbeans.impl.store.CharUtil$1@2aace7a7]) and a value of type [java.lang.ref.SoftReference] (value [java.lang.ref.SoftReference@3d9c9ad4]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak. Dec 13, 2012 12:54:30 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks



(在这种特殊情况下,甚至我的代码都没有这样做)。

谁是罪魁祸首?

这似乎不太公平。 Tomcat 责备我(或我类的用户)做正确的事。

归根结底,这是因为 Tomcat 想要将它提供给我的线程重用于其他 Web 应用程序。 (呃 - 我觉得很脏。)对于 Tomcat 来说,这可能不是一个很好的策略 - 因为线程实际上确实有/导致状态 - 不要在应用程序之间共享它们。

然而,这个政策至少是普遍的,即使它是不可取的。我觉得我有义务 - 作为 ThreadLocal用户,为我的类(class)提供一种“释放”我类(class)附加到各种线程的资源的方法。

但是该怎么办呢?

在这里做什么是正确的?

在我看来,servlet 引擎的线程重用策略似乎与 ThreadLocal 背后的意图不一致。 .

但也许我应该提供一个工具来允许用户说“消失了,与这个类相关的邪恶线程特定状态,即使我无法让线程死亡并让 GC 做它的事情?”。我什至有可能这样做吗?我的意思是,我不能安排 ThreadLocal#remove()在看到 ThreadLocal#initialValue() 的每个线程上调用在过去的某个时间。或者还有其他方法吗?

或者我应该对我的用户说“去给自己找一个像样的类加载器和线程池实现”?

编辑#1:阐明了如何 threadCal用于不知道线程生命周期的 vanilla 实用程序类
编辑#2:修复了 DateSensitiveThing 中的线程安全问题

最佳答案

咳咳,这是旧闻

嗯,这个派对有点晚了。 2007 年 10 月,Josh Bloch(java.lang.ThreadLocal 与 Doug Lea 的合著者)wrote :

"The use of thread pools demands extreme care. Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places."



那时人们还在提示 ThreadLocal 与线程池的不良交互。但乔希确实批准了:

“性能的每线程实例。Aaron 的 SimpleDateFormat 示例(上图)就是这种模式的一个示例。”

一些教训
  • 如果您将任何类型的对象放入任何对象池,您必须提供一种“稍后”删除它们的方法。
  • 如果你使用 ThreadLocal 'pool' ,您可以这样做的选择有限。任何一个:
    a) 你知道 Thread (s) 当您的应用程序完成时,您放置值的位置将终止;或者
    b) 您可以稍后安排调用 ThreadLocal#set() 的同一线程在应用程序终止时调用 ThreadLocal#remove()
  • 因此,您使用 ThreadLocal 作为对象池将在您的应用程序和类的设计上付出沉重的代价。好处不是免费的。
  • 因此,使用 ThreadLocal 可能是一种过早的优化,尽管 Joshua Bloch 在“Effective Java”中敦促您考虑它。

  • 简而言之,决定使用 ThreadLocal 作为对“每个线程实例池”的快速、无竞争访问的一种形式并不是一个轻率的决定。

    注意:除“对象池”之外,ThreadLocal 还有其他用途,并且这些类(class)不适用于 ThreadLocal 仅打算临时设置的情况,或者有真正的每线程状态要保持的情况踪迹。

    库实现者的后果

    对库实现者有一些后果(即使这些库是您项目中的简单实用程序类)。

    任何一个:
  • 您使用 ThreadLocal,充分意识到您可能会用额外的负担“污染”长时间运行的线程。如果您正在实现 java.util.concurrent.ThreadLocalRandom ,可能是合适的。 (如果您没有在 java.* 中实现,Tomcat 可能仍然会提示您图书馆的用户)。有趣的是要注意 java.* 的纪律。很少使用 ThreadLocal 技术。

  • 或者
  • 您使用 ThreadLocal,并为您的类/包提供客户端:
    a) 有机会选择放弃优化(“不要使用 ThreadLocal ......我无法安排清理”);和
    b) 一种清理 ThreadLocal 资源的方法(“可以使用 ThreadLocal ......我可以安排所有使用你调用的线程 LibClass.releaseThreadLocalsForThread() 当我完成它们。

  • 但是,使您的库“难以正确使用”。

    或者
  • 您让您的客户有机会提供他们自己的对象池实现(可能使用 ThreadLocal 或某种同步)。 (“好吧,我可以给你一个 new ExpensiveObjectFactory<T>() { public T get() {...} },如果你觉得真的有必要的话”。

  • 没那么糟糕。如果对象真的那么重要并且创建起来那么昂贵,那么显式池化可能是值得的。

    或者
  • 你决定它对你的应用程序来说没有那么多值(value),并找到一种不同的方法来解决这个问题。那些创建成本高昂的、可变的、非线程安全的对象让你很痛苦……无论如何,使用它们真的是最好的选择吗?

  • 备择方案
  • 常规对象池,以及所有竞争同步。
  • 不池对象 - 只需在本地范围内实例化它们并稍后丢弃。
  • 不池化线程(除非你可以在你喜欢的时候安排清理代码)——不要在 JaveEE 容器中使用你的东西
  • 线程池足够聪明,可以清理 ThreadLocals 而不会对你发牢骚。
  • 线程池在“每个应用程序”的基础上分配线程,然后在应用程序停止时让它们死亡。
  • 线程池容器和应用程序之间的协议(protocol),它允许注册“应用程序关闭处理程序”,容器可以安排在用于服务应用程序的线程上运行......下一个可用。例如。 servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})

  • 很高兴在 future 的 JavaEE 规范中看到关于最后 3 项的一些标准化。

    引导笔记

    实际上,一个 GregorianCalendar 的实例化很轻。这是对setTime()的不可避免的调用这会导致大部分工作。它也不会在线程执行的不同点之间保持任何重要状态。放一个 CalendarThreadLocal不太可能给你超过你付出的代价......除非分析确实显示了 new GregorianCalendar() 中的热点。 .
    new SimpleDateFormat(String)相比之下,它很昂贵,因为它必须解析格式字符串。解析后,对象的“状态”对于同一线程以后的使用很重要。这是一个更好的选择。但是实例化一个新的可能仍然比给你的类额外的责任“更便宜”。

    关于java - 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13852632/

    有关java - 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?的更多相关文章

    1. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

      我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

    2. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    3. ruby - 无法让 RSpec 工作—— 'require' : cannot load such file - 2

      我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳

    4. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    5. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

    6. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

      在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

    7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

      我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

    8. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

      我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

    9. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

      rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

    10. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    随机推荐