<?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/%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>读java concurrency in practice</title>
		<link>http://blog.11034.org/2016-08/java_concurrency_in_practice.html</link>
		<comments>http://blog.11034.org/2016-08/java_concurrency_in_practice.html#comments</comments>
		<pubDate>Thu, 18 Aug 2016 07:15:05 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Life in reading]]></category>
		<category><![CDATA[同步]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[锁]]></category>

		<guid isPermaLink="false">http://blog.11034.org/?p=2875</guid>
		<description><![CDATA[这是一本好书（Java源码作者Doug Lea是作者之一），关于Java并发的各个方面，介绍的很深入，应该算是 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>这是一本好书（Java源码作者Doug Lea是作者之一），关于Java并发的各个方面，介绍的很深入，应该算是进阶一类，很多地方不好理解。一开始打算阅读英文版，倒不是英文看不懂，不过还是有很多内容的确理解起来很费劲，尤其是老外写书的风格（废话特别多 = = 一页密密麻麻的英文，让人找不到关键点在哪儿），看几页就没兴趣再看下去哎&#8230;后有幸找到了中文翻译版，而且翻译的很有水准，拜谢翻译工作者韩锴和方妙。</p>
<p>找不到翻译版的同学，可以私信我提供。</p>
<p><span id="more-2875"></span></p>
<p>可以说，自己对Java真正的并发同步的理解还处于入门阶段，看了一遍书很多地方估计还是没有get到的，不过已经学到不少了。这本书值得以后再细细研读。</p>
<ul>
<li>可重入的概念、可重入锁和java.util.concurrent.locks.ReentrantLock。</li>
</ul>
<p>详见：<strong><a class="row-title" title="编辑“可重入锁、ReentrantLock、AQS、Condition”" href="http://blog.11034.org/wp-admin/post.php?post=2833&amp;action=edit" target="_blank">可重入锁、ReentrantLock、AQS、Condition</a></strong></p>
<ul>
<li>基于AQS的各种同步工具类</li>
</ul>
<p>有ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore、FutureTask、CyclicBarrier等</p>
<p>详见：<strong><a class="row-title" title="编辑“ReentrantReadWriteLock简单分析”" href="http://blog.11034.org/wp-admin/post.php?post=2838&amp;action=edit">ReentrantReadWriteLock简单分析</a>  <a class="row-title" title="编辑“CountDownLatch简单分析”" href="http://blog.11034.org/wp-admin/post.php?post=2845&amp;action=edit">CountDownLatch简单分析</a> <a class="row-title" title="编辑“Semaphore简单分析”" href="http://blog.11034.org/wp-admin/post.php?post=2850&amp;action=edit">Semaphore简单分析</a> <a class="row-title" title="编辑“FutureTask简单分析和用法”" href="http://blog.11034.org/wp-admin/post.php?post=2853&amp;action=edit">FutureTask简单分析和用法</a></strong></p>
<ul>
<li>reordering重排序、happened-before关系、volatile真正语义</li>
<li>基于硬件的原子CAS算法</li>
<li>内置synchronized锁、基于CAS算法的锁、CAS原子类、volatile修饰的性能相关</li>
<li>org.apache.http.annotation包下有一些有用的Annotation，比如ThreadSafe、NotThreadSafe等</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-06-13 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantlock.html" title="可重入锁、ReentrantLock、AQS、Condition">可重入锁、ReentrantLock、AQS、Condition</a></li><li>2015-01-07 -- <a target="_blank" href="http://blog.11034.org/2015-01/prevent_db_duplicate.html" title="防止数据库数据重复的几种方法">防止数据库数据重复的几种方法</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-17 -- <a target="_blank" href="http://blog.11034.org/2016-06/countdownlatch.html" title="CountDownLatch简单分析">CountDownLatch简单分析</a></li></ul><h4 class="related_post_title">看看 Java , Life in reading </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-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><li>2016-06-17 -- <a target="_blank" href="http://blog.11034.org/2016-06/countdownlatch.html" title="CountDownLatch简单分析">CountDownLatch简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2016-08/java_concurrency_in_practice.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Semaphore简单分析</title>
		<link>http://blog.11034.org/2016-06/semaphore.html</link>
		<comments>http://blog.11034.org/2016-06/semaphore.html#comments</comments>
		<pubDate>Tue, 21 Jun 2016 06:36:11 +0000</pubDate>
		<dc:creator><![CDATA[-Flyぁ梦-]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[锁]]></category>

		<guid isPermaLink="false">http://blog.11034.org/?p=2850</guid>
		<description><![CDATA[java.util.concurrent.Semaphore AQS实现的一个变种，用来预先设置一个阈值（可进 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>java.util.concurrent.Semaphore</p>
<p>AQS实现的一个变种，用来预先设置一个阈值（可进入量），然后当做类似共享锁的方式地进行acquire和release，<code class="markdown_inline_code">但是每次acquire不记录当前acquire的线程对象（这是Semaphore和Lock最大的区别）</code>，即线程A操作的acquire可以由线程B来release。也有公平和非公平两种方式。</p>
<p><span id="more-2850"></span></p>
<p>一般用在管理一个对象池，设置初始容量N，每次getObject就acquire，每次returnObject就release，因为没有对对象池本身采用任何同步加锁代码，即使线程A的acquire在阻塞中，线程B的release依然可以进行。</p>
<p>Semaphore的最大容量也可以动态增加，可以通过release和release(int n)持续扩大容量（即使没有任何线程进行acquire）（虽然API名称上不直观）。</p>
<p>AQS的代码解析见：<a class="title" href="/2016-06/reentrantlock.html" target="_blank" rel="bookmark">可重入锁、ReentrantLock、AQS、Condition</a></p>
<p>reducePermits和nonfairTryAcquireShared的区别在于，reducePermits不管减少容量后的state值是多少（可以为负值）阻塞等待CAS完成，nonfairTryAcquireShared则需要判断减少容量后的state值是正值则阻塞等待CAS完成，负值则返回acquire失败回到AQS中加入Sync队列。</p>
<p>&nbsp;</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-16 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantreadwritelock.html" title="ReentrantReadWriteLock简单分析">ReentrantReadWriteLock简单分析</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-17 -- <a target="_blank" href="http://blog.11034.org/2016-06/countdownlatch.html" title="CountDownLatch简单分析">CountDownLatch简单分析</a></li>]]></content:encoded>
			<wfw:commentRss>http://blog.11034.org/2016-06/semaphore.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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>
		<item>
		<title>可重入锁、ReentrantLock、AQS、Condition</title>
		<link>http://blog.11034.org/2016-06/reentrantlock.html</link>
		<comments>http://blog.11034.org/2016-06/reentrantlock.html#comments</comments>
		<pubDate>Mon, 13 Jun 2016 13:51:14 +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=2833</guid>
		<description><![CDATA[本文简单介绍可重入锁的概念，简单介绍java中内置的synchronized锁和java.util.concu [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文简单介绍可重入锁的概念，简单介绍java中内置的<code class="markdown_inline_code">synchronized锁</code>和<code class="markdown_inline_code">java.util.concurrent.locks.ReentrantLock</code>，然后大致介绍下ReentrantLock的代码实现。ReentrantLock和AQS代码逻辑真心复杂和难懂，尤其是AQS中Node的ws值中的SIGNAL值的用法和线程park/unpark的用法，持续几周看了好几天终于算是给弄明白了！<span id="more-2833"></span></p>
<h2>可重入锁</h2>
<p>可重入锁，这是一个理论范畴的概念，具体可自己查询。意思就是，在当前线程已经获取锁的情况下，可以再度获取锁而不会引起死锁。最好理解的就是java代码中最常见的synchronized锁，它就是一个可重入锁，因为同为synchronized修饰的方法A和B，在A中call B，是没有问题的，从A到B的过程就是锁的重入。</p>
<p>所以很好理解，可重入锁需要在内部维持一个进入次数N和占有锁的线程对象S，当N == 0时表示锁闲置，获取锁时set N = 1并set S = currentThread，每次进入锁N++，每次退出锁N&#8211;，直到N == 0锁被释放并set S = null，当然前提所有操作要保证currentThread == S才行。</p>
<h2>synchronized锁和ReentrantLock</h2>
<p>两者都是可重入锁</p>
<p>区别在于：</p>
<table>
<tbody>
<tr>
<td>synchronized</td>
<td>ReentrantLock</td>
</tr>
<tr>
<td>语法内置，自动加锁和释放锁得到保证</td>
<td>手动lock和unlock，unlock需在finally中，否则临界区代码异常会引起死锁</td>
</tr>
<tr>
<td>非公平锁</td>
<td>提供默认的非公平锁和公平锁实现</td>
</tr>
<tr>
<td></td>
<td>加锁API丰富，lock、tryLock、lockInterruptibly、tryLock(timeout)</td>
</tr>
<tr>
<td>利用object或者class作为锁本身来wait和notify</td>
<td>提供Condition机制来await和signal，更灵活和好理解</td>
</tr>
<tr>
<td>使用难度低，效率偏低</td>
<td>使用难度较高，效率更高</td>
</tr>
</tbody>
</table>
<p>虽然效率上Reentrantlock偏高，但如果不熟悉可能会引起更多问题，在大多数情况下理应优先使用synchronized，只有在需要利用ReentrantLock的公平机制、其他lock API的情况下，或者对于效率有着非常严格要求的情况下，才推荐使用ReentrantLock。</p>
<h2>ReentrantLock与AbstractQueuedSynchronizer</h2>
<p>ReentrantLock是AbstractQueuedSynchronizer（AQS）的一个实现（不是子类），绝大部分的实现都在于AQS之内，AQS仅仅暴露了几个API供子类实现，而ReentrantLock仅仅实现了其中两个：</p>
<ul>
<li>protected boolean tryAcquire(int arg)：尝试加锁</li>
<li>protected boolean tryRelease(int arg)：尝试解锁</li>
</ul>
<h3>AQS内部数据结构</h3>
<ul>
<li>静态内部类Node，作为一个双向list的节点，存储有线程对象和一个状态值，以及另外一个变量表明是否为独占</li>
<li>Node head, tail 节点，维护Node的双向list，此为等待加锁的线程节点Sync列表</li>
<li>int state值，供子类使用，可表示不同意义；在Reentrantlock中表示重入锁次数</li>
<li>内部类ConditionObject，每个ConditionObject内部维护一个Node队列，是为等待此condition的线程队列</li>
</ul>
<h3>AQS中Node的waitStatus（以下简称ws值）状态值</h3>
<ul>
<li>0，正常状态，不停地尝试去获取锁</li>
<li>1，CANCELLED，表示此节点已被Cancel，退出竞争锁</li>
<li>-1，SIGNAL，表示当前node在release时后面的节点需要被唤醒</li>
<li>-2，CONDITION，表示当前node在某一个ConditionObject的等待队列中，不在Sync队列中</li>
<li>-3，PROPAGATE</li>
</ul>
<h3>AQS中Node的nextWaiter节点</h3>
<p>nextWaiter == Node.EXCLUSIVE == null，表明为独占</p>
<p>nextWaiter == Node.SHARED== new Node()，表明为共享</p>
<h2>ReentrantLock中的Sync、NonfairSync、FairSync</h2>
<p>Reentrantlock中的Sync才是AQS的直接子类，nonfairTryAcquire()方法实现了非公平锁的实现，并实现了tryRelease()方法，基本就是通过CAS原理，通过AQS的state值和ownerThread来实现，很好理解。</p>
<p>NonfairSync又是Sync的子类，tryAcquire()直接调用nonfairTryAcquire()。</p>
<p>FairSync也是是Sync的子类，单独实现tryAcquire()，比nonfairTryAcquire()多了一个isFirst(current)的判断，就保证了公平性。后面会具体讲。</p>
<h2>AQS中对线程的park和unpark</h2>
<p>利用unsafe实现的，对线程挂起和唤醒的操作。</p>
<p>有2个地方会引起线程park</p>
<ol>
<li>ConditionObject.await方法，使得当前线程节点进入condition队列</li>
<li>AQS.acquireQueued的循环中，当前线程节点的prev节点的ws值为SIGNAL</li>
</ol>
<p>有2个地方会引起线程unpark</p>
<ol>
<li>ConditionObject的doSignal方法，使得线程节点从condition队列移除，添加到Sync队列，若prev节点被Cancel了或者设置prev.ws为SIGNAL失败，则立即激活本节点线程</li>
<li>当前线程节点的prev节点线程抢占锁并release，且prev.ws == SIGNAL，唤醒next节点</li>
</ol>
<h2>ReentrantLock的代码实现</h2>
<p>此部分内容，不考虑cancelAcquire操作、其他lock操作和线程中断等复杂情况，以最基本的逻辑讲述。</p>
<h3>非公平锁的lock</h3>
<ol>
<li>新的线程，在NonfairSync.lock中直接尝试CAS加锁，成功则返回</li>
<li>若失败，call AQS.acquire(1)，1表示进入次数加1</li>
<li>AQS.acquire：!tryAcquire(n) &amp;&amp; acquireQueued(addWaiter(Node.EXCLUSIVE), n)</li>
<li>调用子类的tryAcquire再试一遍加锁：即Sync.nonfairTryAcquire()，通过CAS尝试加锁或者计算重进入，成功则返回</li>
<li>若失败，回到AQS.acquire，执行acquireQueued(addWaiter(Node.EXCLUSIVE), n)</li>
<li>AQS.addWaiter，新建Node节点（独占），通过AQS.enq方法无限循环保证（阻塞式地）添加到Sync队列中</li>
<li>AQS.acquireQueued，无限循环开始：判断节点是否为Sync队列中的first有效节点并call tryAcquire尝试加锁，若成功，移除原head，设置新head = first，返回</li>
<li>循环中：若失败，call AQS.shouldParkAfterFailedAcquire判断node是否需要被park，然后park当前线程</li>
<li>循环结束，回到步骤7</li>
</ol>
<ul>
<li>第1、4步中可以看到，新的线程尝试加锁可以无视Sync队列中等待的其他线程直接尝试加锁（而且代码更简单效率更高，如下图中的黄色快速通道所示），此为非公平性原理</li>
</ul>
<div id="attachment_2840" style="width: 403px" class="wp-caption aligncenter"><a href="http://blog.11034.org/wp-content/uploads/2016/06/sync_queue.png"><img class="size-full wp-image-2840" src="http://blog.11034.org/wp-content/uploads/2016/06/sync_queue.png" alt="AQS中的同步队列与加锁通道" width="393" height="126" /></a><p class="wp-caption-text">AQS中的同步队列与加锁通道</p></div>
<ul>
<li>第7步中得知，在Sync队列中等待的线程，是严格按照先后顺序获取锁的。所以是非严格的非公平性。</li>
<li>第8步中shouldParkAfterFailedAcquire的原理，若node.prev的ws为SIGNAL，返回true; 否则，尝试set node.prev.ws = SIGNAL并返回false（在下一次的循环中，就可能会返回true了）</li>
</ul>
<h3>公平锁的lock</h3>
<p>和非公平锁的区别就在于</p>
<ol>
<li>没有第1步，直接进入AQS.acquire(1)</li>
<li>FairSync.tryAcquire中，需要额外判断Sync队列为空或当前线程是Sync队列中的first才尝试CAS加锁，成功则返回</li>
<li>从第5步开始完全一致</li>
</ol>
<p>取消非公平锁的第1步，调整第4步，就保证了严格按照Sync队列来获得锁，即为公平性。</p>
<h3>unlock</h3>
<p>公平锁和非公平锁的unlock操作是一样的，通过调用AQS.release(1)实现</p>
<ol>
<li>call Sync.tryRelease，就是通过CAS将state &#8211; 1，若最后state &gt; 0，锁还未释放，返回</li>
<li>若最后state == 0（锁被释放），若head节点（上一个加锁成功的节点）的ws值 != 0（就是为SIGNAL的情况下）， call AQS.unparkSuccessor(head)</li>
<li>AQS.unparkSuccessor(node)：先CAS设置node.ws = 0</li>
<li>在node节点后找到第一个没有被cancel（ws值 &lt;= 0）的节点，尝试将其unpark（这里要unpark的next线程节点，不一定真的被park了，就是保证next节点的线程可以work来抢锁）</li>
</ol>
<h2>AQS中Condition的实现</h2>
<p>condition被新建时，就在内部维护了一个队列</p>
<h3>condition.await</h3>
<ol>
<li>addConditionWaiter()，将当前线程新建为Node，设置ws值为CONDITION，添加到condition的队列中</li>
<li>call AQS.fullyRelease，将当前线程获得的锁完全释放（即无论重入几次，state之间减为0）</li>
<li>无限循环开始：当node.ws == CONDITION或node不在Sync队列中，park线程</li>
<li>等待condition被其他线程signal或signalAll了，节点ws值被修改且被移回Sync队列中（循环条件被打破），但此时线程仍在park中</li>
<li>在Sync队列中，node.prev得到锁并释放后，node节点线程被unpark</li>
<li>跳出循环，call acquireQueued()，进入正常锁竞争逻辑</li>
</ol>
<h3>condition.signal和condition.signalAll</h3>
<p>将condition的队列中的first节点或者全部节点，调用condition.doSignal()，然后就是调用AQS.transferForSignal()</p>
<ol>
<li>首先将本节点的ws从CONDITION设为0</li>
<li>call AQS.enq，将节点加入到Sync队列中</li>
<li>判断node.prev.ws值为CANCELLED，或者设置node.prev.ws = SIGNAL失败，直接将本节点线程unpark</li>
</ol>
<p>经过这3步，就可以打破await中的循环条件，然后当node.prev得到锁并释放后，node节点线程被unpark</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-16 -- <a target="_blank" href="http://blog.11034.org/2016-06/reentrantreadwritelock.html" title="ReentrantReadWriteLock简单分析">ReentrantReadWriteLock简单分析</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-17 -- <a target="_blank" href="http://blog.11034.org/2016-06/countdownlatch.html" title="CountDownLatch简单分析">CountDownLatch简单分析</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/reentrantlock.html/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
