<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>-Flyぁ梦- &#187; 多线程</title>
	<atom:link href="http://blog.11034.org/tag/%e5%a4%9a%e7%ba%bf%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.11034.org</link>
	<description></description>
	<lastBuildDate>Sun, 22 Jun 2025 08:59:05 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.2.38</generator>
	<item>
		<title>jdk中SimpleDateFormat的实例线程不安全</title>
		<link>http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html</link>
		<comments>http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html#comments</comments>
		<pubDate>Tue, 01 Mar 2016 03:59:34 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[多线程]]></category>

		<guid isPermaLink="false">http://blog.11034.org/?p=2779</guid>
		<description><![CDATA[线上服碰到诡异问题，解析一段无参数固定代码生成的时间字符串获得时间戳，不定概率出现解析异常，堆栈如下。 产生的 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>线上服碰到诡异问题，解析一段无参数固定代码生成的时间字符串获得时间戳，不定概率出现解析异常，堆栈如下。</p>
<p>产生的原因，因为SimpleDateFormat每次new的代价比较高，固定new一个后重复使用。一开始并没有对输入字符串进行日志打印，就以为是输入字符串的错误，就难以定位到产生问题的原因。</p>
<p><span id="more-2779"></span></p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">[ERROR]2016-03-01 09:03:13,446, [Class]DateTimeUtils, getTimestampFromDateString error : 2013-02-18 09:43:18.0
java.lang.NumberFormatException: For input string: &quot;E.179E11&quot;
    ...
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
    at java.text.DateFormat.parse(DateFormat.java:335)
    at com.netease.popo.common.util.DateTimeUtils.getTimestampFromDateString(DateTimeUtils.java)
    ...</pre></td></tr></table></div>

<p>明明参数传入的字符串是<code class="markdown_inline_code">2013-02-18 09:43:18.0</code>，怎么报异常就变成了<code class="markdown_inline_code">E.179E11</code>（有时候报错是空字符串，有时候提示<code class="markdown_inline_code">multiple points</code>），就很容易联想到是多线程引起的问题了。</p>
<p>解决的办法：</p>
<ol>
<li>每次new SimpleDateFormat(dateTimeFormat).parse(time)，代价比较大</li>
<li><code class="markdown_inline_code">ThreadLocal</code>，给每个线程新建一个SimpleDateFormat对象</li>
<li>线程安全的第三方库，比如<code class="markdown_inline_code">joda-time</code>（org.joda.time）或<code class="markdown_inline_code">date4j</code>（www.date4j.net），前者使用更广泛</li>
</ol>
<ul>
<li>考虑到使用ThreadLocal需要自己管理且JDK对于日期时间API的弱势，引入一个第三方库对于长远来说也很有必要。</li>
<li>虽然易用性上可能不如date4j更简单，考虑到joda-time使用更广泛资料更多这样也保证了安全和稳定性以及后续的维护代价</li>
</ul>
<p>综上决定使用joda-time库。但是joda的parse功能并不强大，必须要结合具体的timeFormat才行，这点date4j要强大的多。</p>
<p>下面是根据时间字符串获取时间戳的一个例子，根据业务需求解析了几种常见的日期时间字符串格式。</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #003399;">String</span> dateFormat <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;yyyy-MM-dd&quot;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #003399;">String</span> dateTimeFormat <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;yyyy-MM-dd HH:mm:ss&quot;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> DateTimeFormatter dateJFormat <span style="color: #339933;">=</span> DateTimeFormat.<span style="color: #006633;">forPattern</span><span style="color: #009900;">&#40;</span>dateFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> DateTimeFormatter dateTimeJFormat <span style="color: #339933;">=</span> DateTimeFormat.<span style="color: #006633;">forPattern</span><span style="color: #009900;">&#40;</span>dateTimeFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> DateTimeFormatter dbJFormat <span style="color: #339933;">=</span> DateTimeFormat.<span style="color: #006633;">forPattern</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;yyyy-MM-dd HH:mm:ss.S&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">final</span> DateTimeFormatter dateTimeMillJFormat <span style="color: #339933;">=</span> DateTimeFormat.<span style="color: #006633;">forPattern</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000066; font-weight: bold;">long</span> getTimestampFromDateString<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> time<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
	<span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>StringUtils.<span style="color: #006633;">isEmpty</span><span style="color: #009900;">&#40;</span>time<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		<span style="color: #000000; font-weight: bold;">return</span> 0L<span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
	<span style="color: #000066; font-weight: bold;">int</span> len <span style="color: #339933;">=</span> time.<span style="color: #006633;">length</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">try</span> <span style="color: #009900;">&#123;</span>
		DateTime dateTime <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>len <span style="color: #339933;">==</span> <span style="color: #cc66cc;">10</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
			dateTime <span style="color: #339933;">=</span> DateTime.<span style="color: #006633;">parse</span><span style="color: #009900;">&#40;</span>time, dateJFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #000000; font-weight: bold;">else</span> <span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>len <span style="color: #339933;">==</span> <span style="color: #cc66cc;">19</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
			dateTime <span style="color: #339933;">=</span> DateTime.<span style="color: #006633;">parse</span><span style="color: #009900;">&#40;</span>time, dateTimeJFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #000000; font-weight: bold;">else</span> <span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>len <span style="color: #339933;">==</span> <span style="color: #cc66cc;">21</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
			dateTime <span style="color: #339933;">=</span> DateTime.<span style="color: #006633;">parse</span><span style="color: #009900;">&#40;</span>time, dbJFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #000000; font-weight: bold;">else</span> <span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>len <span style="color: #339933;">==</span> <span style="color: #cc66cc;">23</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
			dateTime <span style="color: #339933;">=</span> DateTime.<span style="color: #006633;">parse</span><span style="color: #009900;">&#40;</span>time, dateTimeMillJFormat<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #000000; font-weight: bold;">if</span><span style="color: #009900;">&#40;</span>dateTime <span style="color: #339933;">!=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
			<span style="color: #000000; font-weight: bold;">return</span> dateTime.<span style="color: #006633;">getMillis</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #000000; font-weight: bold;">else</span><span style="color: #009900;">&#123;</span>
			m_logger.<span style="color: #006633;">error</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;getTimestampFromDateString joda miss : &quot;</span> <span style="color: #339933;">+</span> time<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
			<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">SimpleDateFormat</span><span style="color: #009900;">&#40;</span>dateTimeFormat<span style="color: #009900;">&#41;</span>.<span style="color: #006633;">parse</span><span style="color: #009900;">&#40;</span>time<span style="color: #009900;">&#41;</span>.<span style="color: #006633;">getTime</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
	<span style="color: #009900;">&#125;</span> <span style="color: #000000; font-weight: bold;">catch</span> <span style="color: #009900;">&#40;</span><span style="color: #003399;">Exception</span> e<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		m_logger.<span style="color: #006633;">error</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;getTimestampFromDateString error : &quot;</span> <span style="color: #339933;">+</span> time, e<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">return</span> 0L<span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<h4  class="related_post_title">看看 多线程</h4><ul class="related_post"><li>2013-05-20 -- <a target="_blank" href="http://blog.11034.org/2013-05/coyote.html" title="Tomcat的Connector：Coyote">Tomcat的Connector：Coyote</a></li><li>2013-05-19 -- <a target="_blank" href="http://blog.11034.org/2013-05/tomcat6.html" title="Tomcat6源码学习">Tomcat6源码学习</a></li><li>2012-10-01 -- <a target="_blank" href="http://blog.11034.org/2012-10/hashmap_concurrent_bug.html" title="HashMap多线程中引发的死循环">HashMap多线程中引发的死循环</a></li></ul><h4 class="related_post_title">看看 Java </h4><ul class="related_post"><li>2016-09-09 -- <a target="_blank" href="http://blog.11034.org/2016-09/64bits_linux_arena_memory.html" title="64位Linux下Java进程堆外内存迷之64M问题">64位Linux下Java进程堆外内存迷之64M问题</a></li><li>2016-08-18 -- <a target="_blank" href="http://blog.11034.org/2016-08/java_concurrency_in_practice.html" title="读java concurrency in practice">读java concurrency in practice</a></li><li>2016-08-05 -- <a target="_blank" href="http://blog.11034.org/2016-08/thread_stop.html" title="线程清理">线程清理</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/futuretask.html" title="FutureTask简单分析和用法">FutureTask简单分析和用法</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Tomcat的Connector：Coyote</title>
		<link>http://blog.11034.org/2013-05/coyote.html</link>
		<comments>http://blog.11034.org/2013-05/coyote.html#comments</comments>
		<pubDate>Mon, 20 May 2013 14:01:09 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[coyote]]></category>
		<category><![CDATA[Socket]]></category>
		<category><![CDATA[Tomcat]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[异步IO]]></category>
		<category><![CDATA[线程池]]></category>

		<guid isPermaLink="false">http://blog.stariy.org/?p=1808</guid>
		<description><![CDATA[上一篇博文讲了Tomcat的系统架构，今天花了一天时间研究了下coyote的源码，大致对普通IO版本有所了解。 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>上一篇博文讲了<a title="Tomcat6源码学习" href="/2013-05/tomcat6.html" target="_blank">Tomcat的系统架构</a>，今天花了一天时间研究了下coyote的源码，大致对普通IO版本有所了解。被同学提醒后发现，Tomcat6已经支持异步IO了，但默认是普通IO版本，需要在Connector的protocol参数中定义为Http11NioProtocol才开启异步IO模式。</p>
<p>主要的代码，Socket的服务在org.apache.tomcat.util.net.JIoEndpoint类，解析处理HTTP请求在org.apache.coyote.http11包的Http11Protocol类和Http11Processor类，org.apache.catalina.connector.CoyoteAdapter负责连接Connector模块（coyote）和Container模块。</p>
<p>线程池默认用的JIoEndpoint类中的子类WorkerStack，若在server.xml配置使用了Executor，则使用org.apache.catalina.core.StandardThreadExecutor（对ThreadPoolExecutor的一层包装）。并发量高的情况下Executor的效率会高一些。<span id="more-1808"></span></p>
<h2>Coyote普通IO流程</h2>
<p>在Catalina主线程启动过程中：</p>
<p>StandardService的init()中，在JIoEndpoint.init()中，ServerSocket(port = 8080, backlog = 100)被new出来。</p>
<p>StandardService的start()中，在JIoEndpoint.start()中，一个新的线程（名叫http-8080-Acceptor-0）被start()，这个由JIoEndpoint.Accept内部类执行run()的线程干的活就是不停地从serverSocket.accept()，然后根据线程池的选择拿一个线程去处理socket。因为处理在新的线程，Acceptor线程继续等待accept()。</p>
<p>若使用默认的WorkerStack线程池，JioEndpoint.getWorkerThread().assign(socket);使Worker线程获得socket实例后，就可以在新线程中处理socket。</p>
<p>若使用Executor线程池，则new出一个JioEndpoint.SocketProcessor()交给Executor.execute()，在新线程中处理socket。</p>
<p>不管哪种线程池方法，新线程中使用一样的代码，调用Http11Protocol$Http11ConnectionHandler.process(socket)，这里使用了一个ConcurrentLinkedQueue用来保存Http11Processor实例，拿一个进行处理。</p>
<p>Http11Protocol.process()具体解析HTTP请求。解析过程中，将参数塞进request和response，再调用CoyoteAdapter.service(request, response)，这样就进入Container模块去调用Servlet代码。</p>
<p>结束后，回收Http11Processor实例，回收线程。</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;流程结束分割线&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>好像代码也没有太难看不懂，大多数复杂的线程同步操作都由java.util.concurrnt包里的类实现了。</p>
<h2>Worker线程的assign()和await()</h2>
<p>Acceptor线程调用Worker.assign( socket )，将socket对象赋给Worker线程，然后Worker线程中的await()方法通过线程同步，Worker线程被唤醒并拿到了socket就可以在自己线程里顺利执行了。</p>
<p>这块线程同步的代码蛮有意思的，而且Worker线程也借助这边的wait()和notifyall()实现挂起和唤醒。</p>
<h2>server.xml参数调优</h2>
<p><strong>开启异步IO</strong></p>
<p>Connector的配置中，将protocol=“HTTP/1.1”改为“org.apache.coyote.http11.Http11NioProtocol”就可以使用nio包的异步IO，效率高不少。</p>
<p>对java.nio包特别是异步IO这一块了解甚浅，而且Tomcat对这一块的实现略复杂，看懂略有难度，努力！</p>
<p>同样地方还能改为“org.apache.coyote.http11.Http11AprProtocol”，据说利用底层OS的异步IO，需要APR和Native这其他两个组件的支持才能工作，效率更高，不是太确定。</p>
<p><strong>开启Executor并在Connector中使用</strong></p>
<p>Executor的2个配置参数，minSpareThreads和maxThreads，对应到ThreadPoolExecutor(int <strong>corePoolSize</strong>,  int <strong>maximumPoolSize</strong>, &#8230;)这个构造的前面2个参数。分别表示活跃线程数和最大线程数。</p>
<p><strong>Connector的参数：</strong></p>
<ul>
<li>maxThreads，对应JIoEndpoint类中的maxThreads变量，默认为200。若有Executor加入则maxThreads被取代。</li>
<li>acceptCount，对应JIoEndpoint类中的backlog变量，默认为100，用作ServerSocket的第二个参数。ServerSocket里等待队列的长度。</li>
</ul>
<h4  class="related_post_title">看看 coyote , Socket , Tomcat , 多线程 , 异步IO , 线程池</h4><ul class="related_post"><li>2013-05-19 -- <a target="_blank" href="http://blog.11034.org/2013-05/tomcat6.html" title="Tomcat6源码学习">Tomcat6源码学习</a></li><li>2016-06-08 -- <a target="_blank" href="http://blog.11034.org/2016-06/tomcat_shutdown.html" title="Tomcat监听shutdown释放数据库连接池">Tomcat监听shutdown释放数据库连接池</a></li><li>2016-06-06 -- <a target="_blank" href="http://blog.11034.org/2016-06/tomcat_https.html" title="Tomcat启用https服务">Tomcat启用https服务</a></li><li>2016-03-17 -- <a target="_blank" href="http://blog.11034.org/2016-03/java_client_socket_exceptions.html" title="Java客户端Socket在服务端重启后的异常情况处理">Java客户端Socket在服务端重启后的异常情况处理</a></li><li>2016-03-01 -- <a target="_blank" href="http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html" title="jdk中SimpleDateFormat的实例线程不安全">jdk中SimpleDateFormat的实例线程不安全</a></li></ul><h4 class="related_post_title">看看 Java </h4><ul class="related_post"><li>2016-09-09 -- <a target="_blank" href="http://blog.11034.org/2016-09/64bits_linux_arena_memory.html" title="64位Linux下Java进程堆外内存迷之64M问题">64位Linux下Java进程堆外内存迷之64M问题</a></li><li>2016-08-18 -- <a target="_blank" href="http://blog.11034.org/2016-08/java_concurrency_in_practice.html" title="读java concurrency in practice">读java concurrency in practice</a></li><li>2016-08-05 -- <a target="_blank" href="http://blog.11034.org/2016-08/thread_stop.html" title="线程清理">线程清理</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/futuretask.html" title="FutureTask简单分析和用法">FutureTask简单分析和用法</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2013-05/coyote.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Tomcat6源码学习</title>
		<link>http://blog.11034.org/2013-05/tomcat6.html</link>
		<comments>http://blog.11034.org/2013-05/tomcat6.html#comments</comments>
		<pubDate>Sun, 19 May 2013 10:27:43 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ClassLoader]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[Servlet]]></category>
		<category><![CDATA[Socket]]></category>
		<category><![CDATA[Tomcat]]></category>
		<category><![CDATA[事件驱动]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[生命周期]]></category>

		<guid isPermaLink="false">http://blog.stariy.org/?p=1803</guid>
		<description><![CDATA[在上一篇博文中说到因为电脑风扇坏了2天于是看完了《How tomcat works》译本，不过这本书是针对To [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>在<a title="小黑风扇坏两天，被逼看完一本书" href="/2013-05/fan_error.html" target="_blank">上一篇博文</a>中说到因为电脑风扇坏了2天于是看完了《How tomcat works》译本，不过这本书是针对Tomcat4和5来讲的比较老了，然后再结合Tomcat6.0.26的源代码调试学习了总共一周的时间，算是大致将Tomcat6.0的一部分工作机制（JSP、JMX、安全管理等模块没研究）给搞清楚了。大概是因为平时基于Java的Web程序写的太多，对Tomcat这一块源码研究特别感兴趣，有时候为了想通一个问题兴奋地吃饭睡觉都在一直想，非常有动力并且也有效率，好久没有这种感觉了。</p>
<p><span id="more-1803"></span>列几点觉得Tomcat设计上比较有意思的地方。</p>
<h2>Connector模块</h2>
<p>Tomcat主要分为Connector模块和Container模块，前者就是处理Socket和HTTP请求，后者是就是常听到的Servlet容器。</p>
<p>Connector模块由于功能比较独立，然后Tomcat内部接口的设计允许第三方组件的使用，就使用了Coyote组件，虽然也是apache下的。有空可以再好好来学习下Coyote的源代码，因为这块涉及了多线程和线程同步、资源优化、效率优化等方面，很值得学习。</p>
<p>Tomcat默认一共占用了3个端口，8080（HTTP/1.1）、8009（AJP/1.3，接受其他服务器转发的请求）和8005（等待SHUTDOWN命令，由Server类在主线程中开启）。其中8080和8009由Connector模块管理，所以默认有2个Connector在运作。</p>
<h2>Container模块</h2>
<p>由Engine、Host、Context、Wrapper四个容器组合而成，分别表示整个引擎、虚拟主机、Web应用、Servlet，通过父子关系连接，然后配套相应的Pipeline和Valve实现过程流。</p>
<h2>Lifecycle接口</h2>
<p>包括启动类（Catalina）、Server、Service、Connector、Container、Executor、Loader、Manager、Realm、Resources、Pipeline都实现了这个接口，通过简单的start()和stop()来递归实现生命周期的管理。</p>
<p>并且这一模块中还用到了LifecycleListener和LifecycleEvent这套事件驱动模型，虽然觉得Tomcat的事件驱动做的比较一般性，注册在某个Lifecycle的LifecycleListener会收到这个Object的所有事件通知，要靠自己去辨别事件类型并作出相应反应，效率较低且定制性弱。</p>
<h2>ClassLoader的设计</h2>
<p>Tomcat为了使类库的私有性，有commonLoader用来加载lib下的jar包，catalinaLoader和sharedLoader分别用来加载Tomcat类库和应用程序共享类库且都以commonLoader为parent。但是在Tomcat6的配置中，默认没有开启catalinaLoader和sharedLoader也没有相应的server和shared目录，导致这3个loader就是同一个commonLoader。这3个loader是StandardClassLoader的实例。</p>
<p>然后对于每一个Web应用程序，都有自己的一个WebappClassLoader，其parent为sharedLoader。这样可以防止不同Web应用程序之间加载互相类库。WebappClassLoader和前面提到的StandardClassLoader都继承自java.net.URLClassLoader，直接从指定目录中读取jar包或class文件。WebappClassLoader直接通过管理Servlet类的字节流来实现Servlet的加载然后缓存。</p>
<p>一般的Servlet只有一个实例，若是实现了STM（SingleThreadModel接口，已Deprecated）的Servlet则会有多个实例缓存着。</p>
<p>但似乎无论怎么设计，都无法避免Web应用程序中的Servlet可以获取到Tomcat启动库中的Bootstrap类（这个包由JVM自带的AppClassLoader加载），默认情况下还可以获取到Tomcat主类库中的所有类（比如Catalina类，因为都是由commonLoader加载，而它又是WebappClassLoader的parent）。尝试过将Tomcat的lib目录中关于Tomcat的类库放到server目录下并配置server.loader属性值（conf/catalina.properties）启动catalinaLoader，这样可以避免Servlet访问到Tomcat主类库，但是这样勉强的同时也阻止了比如ContainerServlet接口（使Servlet能访问Wrapper容器）的运作。</p>
<p>这样的一个坏处就是，可以在Web应用程序的Servlet中获取到Catalina类或者Bootstrap类，再启动一次Tomcat。Servlet中，如果在service方法中这么干会被Tomcat的安全机制给发现使得Tomcat整个关闭，若是配置了loadOnStartup后在init方法中这么干则会引发无限循环导致死机&#8230;</p>
<p>不过通过对Tomcat的ClassLoader的学习，对Java的ClassLoader机制有了大大的实践的了解，无论Tomcat是否真正实现了它设计上的初衷，整个设计思想是值得学习的。</p>
<h2>backgroundProcessor()和后台线程</h2>
<p>Engine容器和Context容器都会通过启动一个后台线程间断执行backgroundProcessor方法，比如重新加载Web应用程序的类文件和资源、扫描Session过期等。</p>
<h2>静态资源</h2>
<p>向Tomcat请求静态资源，和请求Servlet没有太大区别，仍然通过Connector和Container这一流程后，交到Tomcat内部的DefaultServlet来处理读取文件内容并写回客户端，下一次请求会返回304 Not Modified。</p>
<p>所以Tomcat对静态资源的管理比较差速度比较慢，才有apache + tomcat结合的说法吧。</p>
<h4  class="related_post_title">看看 ClassLoader , HTTP , Servlet , Socket , Tomcat , 事件驱动 , 多线程 , 容器 , 生命周期</h4><ul class="related_post"><li>2013-05-20 -- <a target="_blank" href="http://blog.11034.org/2013-05/coyote.html" title="Tomcat的Connector：Coyote">Tomcat的Connector：Coyote</a></li><li>2016-06-08 -- <a target="_blank" href="http://blog.11034.org/2016-06/tomcat_shutdown.html" title="Tomcat监听shutdown释放数据库连接池">Tomcat监听shutdown释放数据库连接池</a></li><li>2016-06-06 -- <a target="_blank" href="http://blog.11034.org/2016-06/tomcat_https.html" title="Tomcat启用https服务">Tomcat启用https服务</a></li><li>2016-03-17 -- <a target="_blank" href="http://blog.11034.org/2016-03/java_client_socket_exceptions.html" title="Java客户端Socket在服务端重启后的异常情况处理">Java客户端Socket在服务端重启后的异常情况处理</a></li><li>2016-03-01 -- <a target="_blank" href="http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html" title="jdk中SimpleDateFormat的实例线程不安全">jdk中SimpleDateFormat的实例线程不安全</a></li></ul><h4 class="related_post_title">看看 Java </h4><ul class="related_post"><li>2016-09-09 -- <a target="_blank" href="http://blog.11034.org/2016-09/64bits_linux_arena_memory.html" title="64位Linux下Java进程堆外内存迷之64M问题">64位Linux下Java进程堆外内存迷之64M问题</a></li><li>2016-08-18 -- <a target="_blank" href="http://blog.11034.org/2016-08/java_concurrency_in_practice.html" title="读java concurrency in practice">读java concurrency in practice</a></li><li>2016-08-05 -- <a target="_blank" href="http://blog.11034.org/2016-08/thread_stop.html" title="线程清理">线程清理</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/futuretask.html" title="FutureTask简单分析和用法">FutureTask简单分析和用法</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2013-05/tomcat6.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>HashMap多线程中引发的死循环</title>
		<link>http://blog.11034.org/2012-10/hashmap_concurrent_bug.html</link>
		<comments>http://blog.11034.org/2012-10/hashmap_concurrent_bug.html#comments</comments>
		<pubDate>Mon, 01 Oct 2012 08:55:06 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[多线程]]></category>

		<guid isPermaLink="false">http://blog.stariy.org/?p=1328</guid>
		<description><![CDATA[串叔发现的问题，觉得很有意思，已有文章作详细描述，把原文翻译地讲一下。原文：http://mailinator [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>串叔发现的问题，觉得很有意思，已有文章作详细描述，把原文翻译地讲一下。原文：http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html。</p>
<p>Java的HashMap本身就不支持多线程，是线程不安全的，不过代码中有一些机制来阻止多线程中的不安全因素，比如在多线程修改操作中抛出ConcurrentModificationException，但是HashMap忽视了数组扩容时候这一修改操作，从而导致可能在多线程中进行了不安全的操作。<span id="more-1328"></span></p>
<p>java.util.HashMap 的 transfer方法：</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000066; font-weight: bold;">void</span> transfer<span style="color: #009900;">&#40;</span>Entry<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> newTable<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
	Entry<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> src <span style="color: #339933;">=</span> table<span style="color: #339933;">;</span>
	<span style="color: #000066; font-weight: bold;">int</span> newCapacity <span style="color: #339933;">=</span> newTable.<span style="color: #006633;">length</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">int</span> j <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> j <span style="color: #339933;">&lt;</span> src.<span style="color: #006633;">length</span><span style="color: #339933;">;</span> j<span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		Entry e <span style="color: #339933;">=</span> src<span style="color: #009900;">&#91;</span>j<span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>e <span style="color: #339933;">!=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			src<span style="color: #009900;">&#91;</span>j<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #339933;">;</span>
			<span style="color: #000000; font-weight: bold;">do</span> <span style="color: #009900;">&#123;</span>
<span style="display:block;background-color: #ffc;">				Entry next <span style="color: #339933;">=</span> e.<span style="color: #006633;">next</span><span style="color: #339933;">;</span></span>				<span style="color: #000066; font-weight: bold;">int</span> i <span style="color: #339933;">=</span> indexFor<span style="color: #009900;">&#40;</span>e.<span style="color: #006633;">hash</span>, newCapacity<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
				e.<span style="color: #006633;">next</span> <span style="color: #339933;">=</span> newTable<span style="color: #009900;">&#91;</span>i<span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
				newTable<span style="color: #009900;">&#91;</span>i<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> e<span style="color: #339933;">;</span>
				e <span style="color: #339933;">=</span> next<span style="color: #339933;">;</span>
			<span style="color: #009900;">&#125;</span> <span style="color: #000000; font-weight: bold;">while</span> <span style="color: #009900;">&#40;</span>e <span style="color: #339933;">!=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
	<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>假设现在HashMap中的数组的size为2（桶0和桶1），现在有2个Entry A{key=3}和 B{key=7}，于是都位于桶1中形成链表，假设A在B之前，就是array[1]=&gt;A=&gt;B=&gt;null。</p>
<p>假设有2个线程T1和T2同时进行扩容操作，即会同时调用transfer方法。假设T1先于T2抢到CPU执行transfer方法执行到第9行后切换到T2执行，然后T2顺利执行完毕，然后T1继续执行完毕，看一下这个过程的具体。</p>
<p>T1先执行到第9行完毕，变量e=A，next=B。</p>
<p>T2顺利执行完毕，新的数组size为4（桶0-3），A和B还是hash到了同一个桶中，变为T2_newArray[3]=&gt;B=&gt;A=&gt;null，此时A.next=null，B.next=A。</p>
<p>T1继续执行，第一次循环完毕，T1_newArray[3]=&gt;A=&gt;null，e=B，next=B，e!=null进入下一个循环，此时A.next=null，B.next=A。</p>
<p>第二次循环完毕，T1_newArray[3]=&gt;B=&gt;A=&gt;null，e=A，next=A，e!=null进入下一个循环，此时A.next=null，B.next=A。</p>
<p>第三次循环完毕，<strong>T1_newArray[3]=&gt;A&lt;=&gt;B</strong>，e=null，next=null，e==null跳出循环，此时<strong>A.next=B，B.next=A</strong>。此时桶3的EntryA和EntryB的next指针互相指向对象，没有一个指向null。</p>
<p>因为T1后执行完毕，HashMap采用T1_newArray作为新的array（见HashMap的resize方法）。</p>
<p>T1和T2的扩容操作完毕，并没有引发任何异常和错误，只是埋下了隐患。</p>
<p>当HashMap继续执行其他get和put操作时，并且得到的hash值为3，即在桶3上进行操作问题就出现了，会进入无限循环，而没有抛出任何异常。</p>
<h2>总结</h2>
<p>不要信任HashMap防止多线程错误的代码，不能对HashMap进行多线程修改的操作，并发读是可以的。</p>
<p>对HashMap进行线程安全的办法有很多，比如使用java.util.concurrent.ConcurrentHashMap或者使用java.util.Collections.synchronizedMap(Map&lt;K,V&gt; m)方法包装HashMap，或者在自己调用HashMap的方法中使用同步机制。</p>
<h4  class="related_post_title">看看 多线程</h4><ul class="related_post"><li>2016-03-01 -- <a target="_blank" href="http://blog.11034.org/2016-03/simpledateformat_thread_not_safe.html" title="jdk中SimpleDateFormat的实例线程不安全">jdk中SimpleDateFormat的实例线程不安全</a></li><li>2013-05-20 -- <a target="_blank" href="http://blog.11034.org/2013-05/coyote.html" title="Tomcat的Connector：Coyote">Tomcat的Connector：Coyote</a></li><li>2013-05-19 -- <a target="_blank" href="http://blog.11034.org/2013-05/tomcat6.html" title="Tomcat6源码学习">Tomcat6源码学习</a></li></ul><h4 class="related_post_title">看看 Java </h4><ul class="related_post"><li>2016-09-09 -- <a target="_blank" href="http://blog.11034.org/2016-09/64bits_linux_arena_memory.html" title="64位Linux下Java进程堆外内存迷之64M问题">64位Linux下Java进程堆外内存迷之64M问题</a></li><li>2016-08-18 -- <a target="_blank" href="http://blog.11034.org/2016-08/java_concurrency_in_practice.html" title="读java concurrency in practice">读java concurrency in practice</a></li><li>2016-08-05 -- <a target="_blank" href="http://blog.11034.org/2016-08/thread_stop.html" title="线程清理">线程清理</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/futuretask.html" title="FutureTask简单分析和用法">FutureTask简单分析和用法</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2012-10/hashmap_concurrent_bug.html/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
