<?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%85%b1%e4%ba%ab%e9%94%81/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>CountDownLatch简单分析</title>
		<link>http://blog.11034.org/2016-06/countdownlatch.html</link>
		<comments>http://blog.11034.org/2016-06/countdownlatch.html#comments</comments>
		<pubDate>Fri, 17 Jun 2016 10:18:30 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[共享锁]]></category>
		<category><![CDATA[锁]]></category>

		<guid isPermaLink="false">http://blog.11034.org/?p=2845</guid>
		<description><![CDATA[也是利用AQS实现的一个变种，用来预先设置一个阈值（任务数）后主线程调用await()持续阻塞，然后由其他线程 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>也是利用AQS实现的一个变种，用来预先设置一个阈值（任务数）后主线程调用await()持续阻塞，然后由其他线程执行任务，执行完后调用countDown()将阈值减1，当所有任务执行完毕，阈值减为0，主线程await()被唤醒跳出阻塞，继续执行。<span id="more-2845"></span></p>
<h2>使用例子</h2>

<div class="wp_syntax"><table><tr><td class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">class</span> CountDownLatchDemo <span style="color: #009900;">&#123;</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;">void</span> main<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> args<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">InterruptedException</span> <span style="color: #009900;">&#123;</span>
    	CountDownLatch latch <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> CountDownLatch<span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// 两个任务线程</span>
    	Worker worker1 <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Worker<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;A&quot;</span>, latch<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    	Worker worker2 <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Worker<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;B&quot;</span>, latch<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    	worker1.<span style="color: #006633;">start</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><span style="color: #666666; font-style: italic;">//</span>
    	worker2.<span style="color: #006633;">start</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><span style="color: #666666; font-style: italic;">//</span>
&nbsp;
    	latch.<span style="color: #006633;">await</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// 阻塞等待所有Worker线程结束</span>
        <span style="color: #003399;">System</span>.<span style="color: #006633;">out</span>.<span style="color: #006633;">println</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;all work done&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000000; font-weight: bold;">class</span> Worker <span style="color: #000000; font-weight: bold;">extends</span> <span style="color: #003399;">Thread</span> <span style="color: #009900;">&#123;</span>
    	<span style="color: #000000; font-weight: bold;">private</span> CountDownLatch latch<span style="color: #339933;">;</span>
    	<span style="color: #000000; font-weight: bold;">public</span> Worker<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> workerName, CountDownLatch latch<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
    	    <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">latch</span> <span style="color: #339933;">=</span> latch<span style="color: #339933;">;</span>
    	<span style="color: #009900;">&#125;</span>
    	<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> run<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
	    <span style="color: #000000; font-weight: bold;">try</span><span style="color: #009900;">&#123;</span>
	        doWork<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: #666666; font-style: italic;">// 必须放在finally中保证能够释放（不然出异常主线程无法恢复）</span>
	    <span style="color: #000000; font-weight: bold;">finally</span><span style="color: #009900;">&#123;</span>
	        latch.<span style="color: #006633;">countDown</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">//线程执行结束, 计数器减1</span>
	    <span style="color: #009900;">&#125;</span>
    	<span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span> 
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<h2>源码分析</h2>
<p>针对AQS的分析见：<a href="http://blog.11034.org/2016-06/reentrantlock.html" target="_blank">可重入锁、ReentrantLock、AQS、Condition</a> 和 <a href="http://blog.11034.org/2016-06/reentrantreadwritelock.html" target="_blank">ReentrantReadWriteLock简单分析</a>。</p>
<p>CountDownLatch的内置Sync类相当简单</p>
<ul>
<li>构造函数，初始化count值，写入AQS的state（相当于已经有count的对象持有锁）</li>
<li>主线程调用await()方法，就是执行AQS.acquireSharedInterruptibly，而Sync.tryAcquireShared，只有当state == 0才能够acquire成功（即只有当count减为0，await才会跳出阻塞）</li>
<li>其他线程调用countDown()方法，就是执行AQS.releaseShared(1)，而Sync.tryReleaseShared，最简单的无限循环阻塞保证release成功</li>
<li>多于count数量的执行countDown()方法，不会再更新state值，state减为0就不变了</li>
<li>多次await也没有意义，在state==0后，await操作就是共享加锁，都可以瞬间成功</li>
</ul>
<h4  class="related_post_title">看看 共享锁 , 锁</h4><ul class="related_post"><li>2016-06-16 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantreadwritelock.html" title="ReentrantReadWriteLock简单分析">ReentrantReadWriteLock简单分析</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-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li><li>2016-06-13 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantlock.html" title="可重入锁、ReentrantLock、AQS、Condition">可重入锁、ReentrantLock、AQS、Condition</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-06/countdownlatch.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>ReentrantReadWriteLock简单分析</title>
		<link>http://blog.11034.org/2016-06/reentrantreadwritelock.html</link>
		<comments>http://blog.11034.org/2016-06/reentrantreadwritelock.html#comments</comments>
		<pubDate>Thu, 16 Jun 2016 11:49:10 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[共享锁]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[锁]]></category>

		<guid isPermaLink="false">http://blog.11034.org/?p=2838</guid>
		<description><![CDATA[AQS的另一个实现，可重入的读写锁，其中读锁是共享锁，写锁是排他锁。用法和ReentrantLock基本一致。 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>AQS的另一个实现，可重入的读写锁，其中读锁是共享锁，写锁是排他锁。用法和ReentrantLock基本一致。<span id="more-2838"></span></p>
<p>ReentrantLock的源码分析见：<a href="http://blog.11034.org/2016-06/reentrantlock.html" target="_blank">可重入锁、ReentrantLock、AQS、Condition</a>，作为本文的铺垫。</p>
<p>ReentrantLock利用AQS的state值作为重入次数记录，所以理论上可以重入int的最大值；ReentrantReadWriteLock同样利用AQS的state值来作为重入次数记录，只不过32位的int值，前16位用来记录读锁的重入次数，后16位用来记录写锁的重入次数，所以读锁和写锁的可重入次数最多都只有65535次。</p>
<p>ReentrantReadWriteLock的代码结构和ReentrantLock基本一致，先有一个Sync类继承自AQS实现了全部的写锁和读锁的加锁、解锁代码，然后再有UnfairSync和FairSync继承自Sync来实现非公平和公平策略，两个子类中只实现了readerShouldBlock()和writerShouldBlock()。不同的是，还有两个新的类，ReadLock和WriteLock，内部持有Sync实例，ReadLock的lock()和unlock()调用acquireShared()和releaseShared()，WriteLock的lock()和unlock()调用acquire()和release()，都只是对Sync实例方法的直接调用，没啥可看的东西。</p>
<h2>Sync类</h2>
<h3>state值拆分</h3>
<p>一开始的SHARED_SHIFT、SHARED_UNIT、MAX_COUNT、EXCLUSIVE_MASK变量就是对state值进行前16位和后16的拆分，还有后面sharedCount()和exclusiveCount()方法。</p>
<h3>针对每个线程的读锁的HoldCounter</h3>
<p>state值的前16位，记录了所有线程的读锁的重入次数，所以每个线程中还要自己单独在记录一下读锁重入次数（才能做到读锁的重入和释放）。这个数据就保存在<code class="markdown_inline_code">HoldCounter</code>类中，并用<code class="markdown_inline_code">ThreadLocalHoldCounter extends ThreadLocal&lt;HoldCounter&gt;</code>类来存储。</p>
<p>HoldCounter cachedHoldCounter这个变量可以无视，缓存上一次进入读锁的HoldCounter对象，没有这个功能也没太大影响每次都从ThreadLocal中取就可以了，只是提高了效率。</p>
<h2>Sync类的子类，UnfairSync和FairSync</h2>
<p>都只实现了writerShouldBlock()和readerShouldBlock()方法。</p>
<p>FairSync简单，所有试图acquire锁都必须是Sync队列中的first才行，保证公平性。</p>
<p>UnfairSync的write操作，没有任何需要Block的场景，而read需要判断Sync队列的first是否是写（独占）请求，表明了写的优先级比较高，读在很多情况下要给写让步，防止写锁饥饿。</p>
<h2>代码实现</h2>
<h3>写锁的实现</h3>
<p>调用AQS.acquire和AQS.release实现，然后通过Sync.tryAcquire()和Sync.tryRelease()实现，和ReentrantLock基本没有差别。在tryAcquire中既要判断state的互斥部分（是否有写锁占用），也要判断state的共享部分（是否有读锁占用）。</p>
<h3>读锁的加锁实现</h3>
<p>调用AQS.acquireShared和AQS.releaseShared实现，然后通过Sync.tryAcquireShared()和Sync.tryReleaseShared()实现。</p>
<h4>Sync.tryAcquireShared（<span style="color: #ff0000;">此方法阻塞至acquire成功或者返回失败</span>）</h4>
<ol>
<li>判断是否被写锁占用且当前线程不是写锁拥有者，return -1</li>
<li>判断读锁重进入总数（所有线程）是否超过最大值</li>
<li>根据公平性原则（Sync子类的readerShouldBlock()方法），判断是否可以加读锁</li>
<li>然后就尝试CAS加锁，若成功则获取当前线程的HoldCounter对象，更新count++（更新缓存hc对象），return 1</li>
<li>CAS miss，call fullTryAcquireShared()</li>
<li>fullTryAcquireShared 循环开始（<span style="color: #ff0000;">此方法阻塞</span>）</li>
<li>判断是否被写锁占用且当前线程不是写锁拥有者，return -1</li>
<li>再判断(rh.count | w) == 0 &amp;&amp; readerShouldBlock(current)，(rh.count | w) == 0就是rh.count == 0 || w == 0（当前线程未占有读锁或未占用写锁，即除了占有写锁后的读锁重进入的情况），都要根据公平性原则（Sync子类的readerShouldBlock()方法），判断是否可以加读锁</li>
<li>判断读锁重入是否超过最大值</li>
<li>尝试CAS加锁，若成功更新rh.count++，return 1</li>
<li>CAS miss，回到循环开始，步骤6</li>
</ol>
<p>官方注释中说，fullTryAcquireShared方法handles CAS misses and reentrant reads not dealt with in tryAcquireShared，其中CAS miss是很好理解的，但是reentrant reads还真没看出来（读重入完全有可能在tryAcquireShared的CAS里就完成了呀，可能作者的意思是读重入因为并发比较高所以容易CAS miss？）。</p>
<h4>AQS.acquireShared</h4>
<p>在执行完Sync.tryAcquireShared失败时，会call AQS.doAcquireShared，将线程添加到AQS的Sync队列中，并循环等待加锁成功，类似独占锁执行的AQS.acquireQueued方法。</p>
<h4>AQS.doAcquireShared</h4>
<ol>
<li>添加节点到AQS的Sync队列中</li>
<li>无限循环开始</li>
<li>判断当前节点若是first节点，到步骤4，若不是到步骤6</li>
<li>call tryAcquireShared，若加锁成功到步骤5，若失败到步骤6</li>
<li>call setHeadAndPropagate，更新Sync队列的head节点，并在一定条件下调用AQS.doReleaseShared，并跳出循环返回</li>
<li>判断当前节点线程是否需要park，并执行线程park</li>
<li>回到循环开始，步骤2</li>
</ol>
<p>doAcquireShared和acquireQueued的区别在于，加锁成功后，acquireQueued仅仅就是更新了head节点，而doAcquireShared不仅更新了head节点，还会在一定条件下call doReleaseShared方法去激活下一个等待的节点。</p>
<h4>AQS.setHeadAndPropagate和AQS.doReleaseShared</h4>
<p>这一点可以理解为，因为doAcquireShared是获取共享锁，获取共享锁的前提是没有独占锁被持有，而共享锁被一方获取后，其他方可以同时去尝试获取共享锁，所以可以来激活下一个等待的是共享模式的节点线程，提高效率。这样子可以把Sync队列中依次顺序的共享锁请求全部打开并满足，提高了并发。</p>
<p>所以这里也可以看出，当一个线程获得写锁后，并不是所有等待写锁的线程都会被激活并获得共享写锁，因为Sync队列中是按照公平原则来的，只要队列中有一个独占锁请求（写锁），后面的共享锁（读锁）请求是依然被阻塞的，按照时间顺序保证了这些读锁请求会在写锁之后执行，读到新的数据。</p>
<h3>读锁的解锁实现</h3>
<p>解锁的简单了，调用Sync.tryReleaseShared()，解锁成功后调用AQS.doReleaseShared()，激活下面节点的线程。</p>
<h4>Sync.tryReleaseShared（<span style="color: #ff0000;">此方法阻塞至release成功，返回是否锁被释放</span>）</h4>
<h3>读锁与写锁的实现区别</h3>
<p>读锁（共享锁）的加锁的阻塞，在Sync.fullTryAcquireShared中也有实现（在Sync中出现acquire失败后才进入AQS里的阻塞实现），而在Sync中实现acquire成功，是不会调用AQS.doReleaseShared去对下面节点进行线程唤醒的（这个就有点不明白了）。</p>
<p>ps：读锁（共享锁）的加锁这一块是最复杂的，理解也还不甚到位。</p>
<p>读锁（共享锁）的解锁也需要在阻塞，也在Sync中实现，因为共享锁在解锁时也存在并发；而写锁（独占锁）是不需要阻塞实现的，因为解锁的时刻肯定是只有当前线程可以操作锁对象，不存在并发。</p>
<h3>tryReadLock和tryWriteLock</h3>
<p>这两个方法acquire的逻辑和普通的lock没有区别，除了少判断了writerShouldBlock()和readerShouldBlock()方法，即失去了公平性保证也抛弃了写优先于读的策略。</p>
<h4  class="related_post_title">看看 共享锁 , 并发 , 锁</h4><ul class="related_post"><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-06-17 -- <a target="_blank" href="http://blog.11034.org/2016-06/countdownlatch.html" title="CountDownLatch简单分析">CountDownLatch简单分析</a></li><li>2016-06-13 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantlock.html" title="可重入锁、ReentrantLock、AQS、Condition">可重入锁、ReentrantLock、AQS、Condition</a></li><li>2016-06-21 -- <a target="_blank" href="http://blog.11034.org/2016-06/semaphore.html" title="Semaphore简单分析">Semaphore简单分析</a></li><li>2015-01-07 -- <a target="_blank" href="http://blog.11034.org/2015-01/prevent_db_duplicate.html" title="防止数据库数据重复的几种方法">防止数据库数据重复的几种方法</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-06/reentrantreadwritelock.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
