<?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>并发编程归档 - 枫阿雨&#039;s blog</title>
	<atom:link href="https://www.crazyfay.com/tag/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.crazyfay.com/tag/并发编程/</link>
	<description>CrazyFay</description>
	<lastBuildDate>Tue, 04 Apr 2023 03:55:09 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.2</generator>

<image>
	<url>https://www.crazyfay.com/wp-content/uploads/2023/04/cropped-DockerGopher-32x32.png</url>
	<title>并发编程归档 - 枫阿雨&#039;s blog</title>
	<link>https://www.crazyfay.com/tag/并发编程/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>《Java并发编程的艺术》学习笔记(四) – Volatile 全解读</title>
		<link>https://www.crazyfay.com/2022/03/06/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e5%9b%9b-volatile-%e5%85%a8%e8%a7%a3%e8%af%bb/</link>
					<comments>https://www.crazyfay.com/2022/03/06/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e5%9b%9b-volatile-%e5%85%a8%e8%a7%a3%e8%af%bb/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sun, 06 Mar 2022 12:26:46 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=172</guid>

					<description><![CDATA[<p>Volatile 全解读 Volatile 的定义 在多线程并发编程中synchronized和volatil [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/03/06/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e5%9b%9b-volatile-%e5%85%a8%e8%a7%a3%e8%af%bb/">《Java并发编程的艺术》学习笔记(四) – Volatile 全解读</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>Volatile 全解读</h1>
<h2>Volatile 的定义</h2>
<p>在多线程并发编程中synchronized和volatile都扮演着重要的角色，volatile是轻量级的synchronized，它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时，另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话，它比synchronized的使用和执行成本更低，因为它不会引起线程上下文的切换和调度。</p>
<p>Java语言规范第3版中对volatile的定义如下：Java编程语言允许线程访问共享变量，为了确保共享变量能被准确和一致地更新，线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile，在某些情况下比锁要更加方便（读多写少）。如果一个字段被声明成volatile，Java线程内存模型确保所有线程看到这个变量的值是一致的。</p>
<p><strong>保证可见性； 禁止指令重排序。</strong></p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/8cfc4044d600e46e7befa11cb614af76.png" alt="截图" /></p>
<h2>Volatile 的实现原理 - 可见性</h2>
<p>volatile是如何来保证可见性的呢？对volatile进行写操作时，CPU会做什么事情。</p>
<p>如 Java代码如下。</p>
<pre><code class="language-java">instance = new Singleton();                 // instance是volatile变量</code></pre>
<p>转变成汇编代码，如下。</p>
<pre><code class="language-shell">0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);</code></pre>
<p>有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码，通过查IA-32架构软件开发者手册可知，Lock前缀的指令在多核处理器下会引发了两件事情。</p>
<p><strong>1）将当前处理器缓存行的数据写回到系统内存（主存。声言Lock信号）。 2）这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。</strong></p>
<ul>
<li>Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间，声言处理器的LOCK#信号。在多处理器环境中，LOCK#信号确保在声言该信号期间，处理器可以独占任何共享内存（主存只有当前处理器一个人可以访问，锁总线）。但是，<strong>在最近的处理器里，LOCK＃信号一般不锁总线，而是锁缓存，毕竟锁总线开销的比较大。</strong> 对于Intel486和Pentium处理器，在锁操作时，总是在总线上声言LOCK#信号。但在P6和目前的处理器中，如果访问的内存区域已经缓存在处理器内部，则不会声言LOCK#信号。相反，它会锁定这块内存区域的缓存并回写到内存（总线锁定声言lock信号的，为了提高性能，高级的处理器走的是缓存锁定，改哪里锁定哪里，所以不需要声言lock信号。），并使用缓存一致性机制来确保修改的原子性，此操作被称为“<strong>缓存锁定</strong>”，缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。</li>
<li>一个处理器的缓存回写到内存会导致其他处理器的缓存无效。IA-32处理器和Intel 64处理器使用MESI（修改、独占、共享、无效）控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候，IA-32和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。处理器使用<strong>嗅探技术</strong>保证它的内部缓存、系统内存和其他处理器的缓存的<strong>数据在总线上保持一致</strong>。例如，在Pentium和P6 family处理器中，如果通过嗅探一个处理器来检测其他处理器打算写内存地址，而这个地址当前处于共享状态，那么正在嗅探的处理器将使它的缓存行无效，在下次访问相同内存地址时，强制执行缓存行填充。</li>
</ul>
<p><strong>volatile可见性原理总结：</strong></p>
<p>为了提高处理速度，<strong>处理器不直接和内存进行通信，而是先将系统内存的数据读到内部缓存</strong>（L1，L2或其他）后再进行操作，但操作完不知道何时会写到内存。如果<strong>对声明了volatile的变量进行写操作，JVM就会向处理器发送一条Lock前缀的指令，将这个变量所在缓存行的数据写回到系统内存</strong>。但是，就算写回到内存，如果其他处理器缓存的值还是旧的，再执行计算操作就会有问题。所以，在多处理器下，为了保证各个处理器的缓存是一致的，就会<strong>实现缓存一致性协议</strong>，每<strong>个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期</strong>了，当处理器<strong>发现自己缓存行对应的内存地址被修改，就会将当前处理器的缓存行设置成无效状态</strong>，当处理器对这个数据进行修改操作的时候，会<strong>重新从系统内存中把数据读到处理器缓存里</strong>。</p>
<h2>Volatile 使用的优化</h2>
<p>著名的Java并发编程大师Doug lea在JDK 7的并发包里新增一个队列集合类Linked-TransferQueue，它在使用volatile变量时，用一种追加字节的方式来优化队列出队和入队的性能。LinkedTransferQueue的代码如下。</p>
<pre><code class="language-java">//队列中的头部节点
private transient final PaddedAtomicReference&lt;QNode&gt; head;
//队列中的尾部节点
private transient final PaddedAtomicReference&lt;QNode&gt; tail;
static final class PaddedAtomicReference &lt;T&gt; extends AtomicReference T&gt; {
  //使用很多4个字节的引用追加到64个字节
  Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
  PaddedAtomicReference(T r) {
    super(r);
  }
}
public class AtomicReference &lt;V&gt; implements java.io Serializable {
  private volatile V value;
  //省略其他代码
}</code></pre>
<p>追加字节能优化性能？这种方式看起来很神奇，但如果深入理解处理器架构就能理解其中的奥秘。让我们先来看看LinkedTransferQueue这个类，它使用一个内部类类型来定义队列的头节点（head）和尾节点（tail），而这个内部类PaddedAtomicReference相对于父类AtomicReference只做了一件事情，就是将共享变量追加到64字节。我们可以来计算下，一个对象的引用占4个字节，它追加了15个变量（共占60个字节），再加上父类的value变量，一共64个字节。</p>
<h2>Volatile 实现原理 - 禁止指令重排序</h2>
<p>为了实现volatile的内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说，发现一个最优布置来最小化插入屏障的总数几乎不可能。为此，JMM采取保守策略。</p>
<p>下面是基于保守策略的JMM内存屏障插入策略。</p>
<ul>
<li>在每个volatile<strong>写操作的前面</strong>插入一个<strong>StoreStore</strong>屏障。</li>
<li>在每个volatile<strong>写操作的后面</strong>插入一个<strong>StoreLoad</strong>屏障。</li>
<li>在每个volatile<strong>读操作的后面</strong>插入一个<strong>LoadLoad</strong>屏障。</li>
<li>在每个volatile<strong>读操作的后面</strong>插入一个<strong>LoadStore</strong>屏障。</li>
</ul>
<p>上述内存屏障插入策略非常保守，但它可以保证在任意处理器平台，任意的程序中都能得到正确的volatile内存语义。</p>
<p>如图：StoreStore屏障可以保证在volatile写之前，其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/201f8f17656a670fa90994eeed14edf5.png" alt="截图" /></p>
<p>这里比较有意思的是，volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障（比如，一个volatile写之后方法立即return）。<strong>为了保证能正确实现volatile的内存语义，JMM在采取了保守策略：在每个volatile写的后面</strong>，<del>或者在每个volatile读的前面</del> <strong>插入一个StoreLoad屏障</strong>。<strong>从整体执行效率的角度考虑，JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障</strong>。<strong>因为volatile写-读内存语义的常见使用模式是：一个写线程写volatile变量，多个读线程读同一个volatile变量。</strong> 当读线程的数量大大超过写线程时，选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点：<strong>首先确保正确性，然后再去追求执行效率</strong>。</p>
<p>图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/3e5e1212181221a5a85127a22fe906cf.png" alt="截图" /></p>
<p>上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时，只要不改变volatile写-读的内存语义，编译器可以根据具体情况省略不必要的屏障。下面通过具体的示例代码进行说明。</p>
<pre><code class="language-java">class VolatileBarrierExample {
  int a;
  volatile int v1 = 1;
  volatile int v2 = 2;
  void readAndWrite() {
    int i= v1;  //第一个volatile读
    int j = v2; //第二个volatile读
    a = i+j;  //普通写
    v1 = i+ 1;  //第一个volatile写
    v2 = j * 2; //第二个volatile写
  }
  ... //其他方法
}</code></pre>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/5d46837ff8fdcff20412469456f9956d.png" alt="截图" /></p>
<h2>JSR-133增强volatile的内存语义</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/11a82582e16a251bbdd77c6532e3e494.png" alt="截图" /></p>
<ul>
<li><strong>当第二个操作是volatile写时，不管第一个操作是什么，都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。</strong></li>
<li><strong>当第一个操作是volatile读时，不管第二个操作是什么，都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。</strong></li>
<li>当第一个操作是volatile写，第二个操作是volatile读时，不能重排序。</li>
</ul>
<p>在JSR-133之前的旧Java内存模型中，虽然不允许volatile变量之间重排序，但旧的Java内存模型允许volatile变量与普通变量重排序。这样会产生问题。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/7dbbf2f6971b6b2c076e1628ff01b4e4.png" alt="截图" /></p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/6175e55461d86a8ff60df5322a3e1054.png" alt="截图" /></p>
<p>在旧的内存模型中，当1和2之间没有数据依赖关系时，1和2之间就可能被重排序（3和4类似）。其结果就是：读线程B执行4时，不一定能看到写线程A在执行1时对共享变量的修改。</p>
<h2>未使用 Volatile 下的双重检查锁</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/c3fe2d403cc0161d26b49cb4baedebf2.png" alt="截图" /></p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/e6dd888a2f11cfb8109d0ff4cd328aa5.png" alt="截图" /></p>
<h2>基于 Volatile 的解决方案</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/1d27a9989c6448fe04d4c94272235c3e.png" alt="截图" /></p>
<p>大众式的讲解。因为面试过程中，绝大多数面试官都认为是volatile禁止了 new 对象里边三行代码的重排序。 因为new instance 他是一个 JVM 指令码，对应的是 new 指令。 Volatile能够保障单个JVM 指令的原子性，所以此处， <strong>new instace相当于是volatiole写</strong>，<strong>会在 new instance前加 storestore，后加storeload屏障，b线程就必须在storeload 屏障后边读取</strong>。（不建议面试使用。）实质上，new对象里边的三个小步骤，依然可以重排序，真正的控制是在外层的内存屏障控制。</p>
<h2>基于类初始化的解决方案</h2>
<p>JVM在类的初始化阶段（即在Class被加载后，且被线程使用之前），会执行类的初始化。<strong>在执行类的初始化期间，JVM会去获取一个锁</strong>。这个锁可以同步多个线程对同一个类的初始化。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/fa45697037d57f5a8f5520c95f24c950.png" alt="截图" /></p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/03/06/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e5%9b%9b-volatile-%e5%85%a8%e8%a7%a3%e8%af%bb/">《Java并发编程的艺术》学习笔记(四) – Volatile 全解读</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/03/06/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e5%9b%9b-volatile-%e5%85%a8%e8%a7%a3%e8%af%bb/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>《Java并发编程的艺术》学习笔记(三) – Java内存模型 &#8211; JMM</title>
		<link>https://www.crazyfay.com/2022/02/21/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%89-java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b-jmm/</link>
					<comments>https://www.crazyfay.com/2022/02/21/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%89-java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b-jmm/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Mon, 21 Feb 2022 03:23:58 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=170</guid>

					<description><![CDATA[<p>Java内存模型 - JMM Java内存模型技术 Java的并发采用的是共享内存模型，Java线程之间的通信 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/02/21/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%89-java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b-jmm/">《Java并发编程的艺术》学习笔记(三) – Java内存模型 &#8211; JMM</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>Java内存模型 - JMM</h1>
<h2>Java内存模型技术</h2>
<p>Java的并发采用的是共享内存模型，Java线程之间的通信总是隐式进行，整个通信过程对程序员完全透明。</p>
<p>在Java中，所有实例域、静态域和数组元素都存储在堆内存中，堆内存在线程之间共享（“共享变量”这个术语代指实例域，静态域和数组元素）。局部变量（Local Variables），方法定义参数（Java语言规范称之为Formal Method Parameters）和异常处理器参数（Exception Handler Parameters）不会在线程之间共享，它们不会有内存可见性问题，也不受内存模型的影响。</p>
<p>Java线程之间的通信由Java内存模型（本文简称为JMM）控制，JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看，JMM定义了线程和主内存之间的抽象关系：线程之间的共享变量存储在主内存（Main Memory）中，每个线程都有一个私有的本地内存（Local Memory），本地内存中存储了该线程以读/写共享变量的副本。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/980d5d9e76d066f5ab71aec43658e7df.png" alt="截图" /></p>
<h2>指令重排序</h2>
<p>在执行程序时，为了提高性能，编译器和处理器常常会对指令做重排序。重排序分3种类型。</p>
<p>1）编译器优化的重排序。编译器在<strong>不改变单线程程序语义</strong>的前提下，可以重新安排语句的执行顺序。 2）指令级<strong>并行</strong>的重排序。现代处理器采用了指令级并行技术（Instruction-Level Parallelism，ILP）来将多条指令重叠执行。如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序。 3）内存系统的重排序。由于处理器使用缓存和读/写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/88cc1dd68b863112013f0f5288979aa7.png" alt="截图" /></p>
<p>对于处理器重排序，JMM的处理器重排序规则会要求 <strong>Java编译器</strong> 在生成指令序列时，插入特定类型的<strong>内存屏障</strong>（Memory Barriers，Intel称之为Memory Fence）指令，通过内存屏障指令来禁止特定类型的处理器重排序。</p>
<p>JMM属于语言级的内存模型，它确保在不同的编译器和不同的处理器平台之上，通过禁止特定类型的编译器重排序和处理器重排序，为程序员提供一致的内存可见性保证。</p>
<h2>内存屏障</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/96f7b943fcc0dfc86cdc594e25c758fc.png" alt="截图" /></p>
<p><strong>StoreLoad Barriers</strong>是一个“全能型”的屏障，它同时具有其他3个屏障的效果。执行该屏障开销会很昂贵，因为当前处理器通常要把<strong>写缓冲区中的数据全部刷新到内存中</strong>（Buffer Fully Flush）。</p>
<h2>Happen-Before 原则</h2>
<p>happens-before是JMM最核心的概念。对应Java程序员来说，理解happens-before是理解JMM的关键。</p>
<p>从JDK 5开始，Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中，如果一个操作执行的结果需要对另一个操作可见，那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内，也可以是在不同线程之间。</p>
<p>与程序员密切相关的happens-before规则如下。 1）程序顺序规则：一个线程中的每个操作，happens-before于该线程中的任意后续操作。 2）监视器锁规则：对一个锁的解锁，happens-before于随后对这个锁的加锁。 3）volatile变量规则：对一个volatile域的写，happens-before于任意后续对这个volatile域的读。 4）传递性：如果A happens-before B，且B happens-before C，那么A happens-before C。 5）start()规则：如果线程A执行操作ThreadB.start()（启动线程B），那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。 6）join()规则：如果线程A执行操作ThreadB.join()并成功返回，那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。</p>
<p>两个操作之间具有happens-before关系，并不意味着前一个操作必须要在后一个操作之前执行！happens-before仅仅要求前一个操作（执行的结果）对后一个操作可见，且前一个操作按顺序排在第二个操作之前</p>
<h2>as-if-serial语义</h2>
<p>as-if-serial语义的意思是：不管怎么重排序（编译器和处理器为了提高并行度），（<strong>单线程</strong>）程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。</p>
<p>为了遵守as-if-serial语义，编译器和处理器不会对存在数据依赖关系的操作做重排序，因为这种重排序会改变执行结果。但是，如果操作之间不存在数据依赖关系，这些操作就可能被编译器和处理器重排序。</p>
<p><strong>happens-before关系本质上和as-if-serial语义是一回事。</strong></p>
<p><strong>as-if-serial语义保证单线程内程序的执行结果不被改变，happens-before关系保证正确同步的多线程程序的执行结果不被改变。 as-if-serial语义给编写单线程程序的程序员创造了一个幻境：单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境：正确同步的多线程程序是按happens-before指定的顺序来执行的。 as-if-serial语义和happens-before这么做的目的，都是为了在不改变程序执行结果的前提下，尽可能地提高程序执行的并行度。</strong></p>
<h2>锁的获取与释放的内存语义</h2>
<pre><code class="language-java">class MonitorExample {
    int a = 0;
    public synchronized void writer() {　　　　 // 1
        a++;　　　　　　　　　　                // 2
    }　　　　　　　　　　　　                   // 3
    public synchronized void reader() {　　　   // 4
        int i = a;　　　　　　　　              // 5
        ……
    }　　　　　　　　　　　　                   // 6
}</code></pre>
<ul>
<li>线程A释放一个锁，实质上是线程A向接下来将要获取这个锁的某个线程发出了（线程A对<strong>共享变量所做修改</strong>的）消息。</li>
<li>线程B获取一个锁，实质上是线程B接收了之前某个线程发出的（在释放这个锁之前对共享变量所做修改的）消息。</li>
<li>线程A释放锁，随后线程B获取这个锁，这个过程实质上是线程A通过主内存向线程B发送消息。（<strong>隐式通信</strong>）</li>
</ul>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/02/21/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%89-java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b-jmm/">《Java并发编程的艺术》学习笔记(三) – Java内存模型 &#8211; JMM</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/02/21/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%89-java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b-jmm/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>《Java并发编程的艺术》学习笔记(二) – synchronized 全解读</title>
		<link>https://www.crazyfay.com/2022/02/08/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%ba%8c-synchronized-%e5%85%a8%e8%a7%a3%e8%af%bb/</link>
					<comments>https://www.crazyfay.com/2022/02/08/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%ba%8c-synchronized-%e5%85%a8%e8%a7%a3%e8%af%bb/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Tue, 08 Feb 2022 08:17:12 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=168</guid>

					<description><![CDATA[<p>synchronized 全解读 Synchronized的特性 有序性 读读、写写、写读、读写 都是互斥的， [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/02/08/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%ba%8c-synchronized-%e5%85%a8%e8%a7%a3%e8%af%bb/">《Java并发编程的艺术》学习笔记(二) – synchronized 全解读</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>synchronized 全解读</h1>
<h2>Synchronized的特性</h2>
<ol>
<li>
<p>有序性</p>
<p>读读、写写、写读、读写 都是互斥的，只有一条线程拿到当前的锁，当前锁不释放，其他线程只能处于BLOCK状态，等待锁的释放，然后加入下一步的竞争</p>
</li>
<li>
<p>可见性</p>
<p>完全排他</p>
</li>
<li>
<p>原子性</p>
<p>本质上是线程互斥保证的原子性</p>
</li>
<li>
<p>可重入性</p>
</li>
</ol>
<h2>Synchronized锁升级 - Mark Word（32bit）</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/2654f92ba41f65199f03211c67f7397c.png" alt="截图" /></p>
<h2>Synchronized锁升级 - 偏向锁</h2>
<ol>
<li>
<p>至少JDK1.6 版本且开启了偏向锁配置。 偏向锁在Java 6和Java 7里是默认启用的，但是它在应用程序启动几秒钟之后才激活，如有必要可以使用JVM参数来关闭延迟：-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态，可以通过JVM参数关闭偏向锁：-XX:-UseBiasedLocking=false，那么程序默认会进入轻量级锁状态。</p>
</li>
<li>
<p>被加锁的对象，没有真正、或者隐式的调用父类 Object 里边的hashcode方法。</p>
<p>如果一旦调用了object的hashcode方法，那么我们的对象头里边就有真正的hashcode值了，如果偏向锁来进行markword的替换，至少要提供一个保存hashcode的地方吧？可惜的是，偏向锁并没有地方进行markword的保存，只有轻量级锁才会有“displace mark word”</p>
</li>
</ol>
<p>为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时，会在<strong>对象头</strong>（存储线程id） <strong>和栈帧中的锁记录里</strong>（线程有自己的栈帧，LOCK RECORD: 存储当前线程id） 存储锁偏向的线程ID，以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁，只需简单地测试一下对象头的Mark Word里是否存储着指向 <strong>当前线程的偏向锁。</strong>（id的匹配） 如果测试成功，表示线程已经获得了锁。如果测试失败，则需要再测试一下Mark Word中偏向锁的标识是否设置成1（表示当前是偏向锁）：如果没有设置，则使用CAS竞争锁；如果设置了，则尝试使用CAS将对象头的 <strong>偏向锁指向当前线程</strong>（ 其实是cas竞争替换 线程id）。</p>
<blockquote>
<p>注：相当于给每个每个对象固定“偏向”某个线程，没有竞争时只需要线程确认栈内记录的身份是否还偏向自己，如果身份还是匹配，则不需要真正的加锁。如果mark word中偏向的线程不是自己，则检测是否是偏向锁，如果不是则CAS锁升级争夺轻量级锁。如果目标是偏向锁，则用CAS更改mark word中偏向的线程</p>
</blockquote>
<h2>Synchronized锁升级 - 偏向锁的撤销</h2>
<p>偏向锁使用了一种等到竞争出现才释放锁的机制，一旦有竞争则升级到轻量级锁（简单且不严谨的说法）</p>
<p><strong>偏向锁使用了一种等到竞争出现才释放锁的机制</strong>，所以当其他线程尝试竞争偏向锁时，持有偏向锁的线程才会释放锁。偏向锁的撤销，需要等待全局安全点（在这个时间点上没有正在执行的字节码）。它会首先暂停拥有偏向锁的线程，然后检查持有偏向锁的线程是否活着，如果线程不处于活动状态，则将对象头设置成无锁状态；如果线程仍然活着，拥有偏向锁的栈会被执行，遍历偏向对象的锁记录，栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程，要么恢复到无锁或者标记对象不适合作为偏向锁，最后唤醒暂停的线程。 （包括锁不升级时的严谨说法）</p>
<ol>
<li>A线程获取偏向锁，并且A线程死亡退出。B线程争抢偏向锁，会直接升级当前对象的锁为轻量级锁。这只是针对我们争抢了一次。</li>
<li>A线程获取偏向锁，并且A线程没有释放偏向锁，还在syhnc的代码块里边。B线程此时过来争抢偏向锁，会直接升级为重量级锁。</li>
<li>A线程获取偏向锁，并且A线程释放了锁，但是A线程并没有死亡还在活跃状态。B线程过来争抢，会直接升级为轻量级锁。 综上所述，当我们尝试第一次竞争偏向锁时，如果A线程已经死亡，升级为轻量级锁；如果A线程未死亡，并且未释放锁，直接升级为重量级锁；如果A线程未死亡，并且已经释放了锁，直接升级为轻量级锁。</li>
<li><strong>A线程获取偏向锁，并且A线程没有释放偏向锁，还在syhnc的代码块里边。B线程多次争抢锁，会在加锁过程中采用重量级锁；但是，一旦锁被释放，当前对象还是会以轻量级锁的初始状态执行。</strong></li>
<li><strong>A线程获取偏向锁，并且A线程释放了锁，但是A线程并没有死亡还在活跃状态。B线程过来争抢。部分争抢会升级为轻量级锁；部分争抢会依旧保持偏向锁。</strong></li>
</ol>
<h2>Synchronized锁升级 - 偏向锁的重偏向与批量撤销</h2>
<p>偏向锁状态变化与最终升级为轻量级锁：</p>
<ol>
<li>A 线程获取偏向锁成功，已经退出执行不再是活跃线程； B线程过来获取偏向锁，默认前20次直接升级为轻量级锁 （触发批量重偏向阈值之前， 默认为 20次争抢，不同机器环境参数配置不一样）；</li>
<li>A 线程获取偏向锁成功，已经退出执行不再是活跃线程； B线程过来获取偏向锁，默认20次以后，直接偏向线程 B。达到40次阈值后，若再有其他线程C过来争抢，则触发批量撤销。该对象不再有任何偏向锁的情况。</li>
</ol>
<p><strong>批量重偏向：</strong> 当我们的一个对象，Object 类，在经过<strong>默认 20次</strong>的争抢的情况下，会将后边的所有争抢从新偏向争抢的线程。当B线程争抢第 18 次的时候，触发了批量重偏向的阈值；在第20次以及以后的争抢里，jvm会将线程偏向线程b，因为jvm认为，这个对象更加适合线程B</p>
<p><strong>批量撤销：</strong> 如果基于批量重偏向的基础上，还在继续进行争抢达到40次，并且有第三条线程C加入了，这个时候会触发批量撤销。JVM会标记该对象不能使用偏向锁，以后新创建的对象，直接以轻量级锁开始。 这个时候，才是真正的完成了锁升级。</p>
<p><strong>真正的锁升级，是依赖于 class 的（加锁对象实例对应的类），而并不是依赖于 某一个 new出来的对象（偏向锁升级为轻量级锁）。</strong></p>
<h2>Synchronized锁升级 - 轻量级锁加锁与解除</h2>
<p>（1）轻量级锁加锁 线程在执行同步块之前，JVM会先在当前线程的栈桢中创建用于存储锁记录的空间（Lock Record记录），并将对象头中的Mark Word（前30位 （25位的hashcode，4位的分代年龄，1位是否为偏向锁））复制到锁记录中，官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针（指向线程栈帧里边的Lock Record的指针）。如果成功，当前线程获得锁，如果失败，表示其他线程竞争锁，当前线程便尝试使用<strong>自旋</strong>来获取锁。</p>
<p>（2）轻量级锁解锁 轻量级解锁时，会使用原子的CAS操作将Displaced Mark Word（Lock Record记录）替换回到对象头，如果成功，则表示没有竞争发生。如果失败，表示当前锁存在竞争，锁就会膨胀成重量级锁。</p>
<p>轻量级锁升级为重量级锁：这个时候，只要我们的线程发生了竞争，并且CAS替换失败，就会发起锁膨胀，升级为重量级锁（针对的是一个对象实例）</p>
<h2>Synchronized锁升级 - 轻量级锁升级为重量级锁</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/e778fea7130be50e20d80f744622e3c6.png" alt="截图" /></p>
<p>轻量级锁—重量级锁： 释放锁（前四步）并唤醒等待线程</p>
<ol>
<li>线程1 初始化monitor 对象；</li>
<li>将状态设置为膨胀中（inflating）；</li>
<li>将monitor里边的header属性，set称为对象的markword；（将自己lock record里边的存放的mark word的hashcode，分代年龄，是否为偏向锁 set 到 objectmonitor对象的header属性里）</li>
<li>设置对象头为重量级锁状态（标记为改为00）；然后将前30位指向第1步初始化的monitor 对象；（真正的锁升级是由线程1操控的）</li>
<li>唤醒线程2；</li>
<li>线程2 开始争抢重量级锁。（线程2就干了一件事儿，就是弄了一个临时的重量级锁指针吧？还不是最后的重量级锁指针。因为最后的重量级锁指针是线程1初始化的并且是线程1修改的。 而且，线程2被唤醒之后，还不一定能够抢到这个重量级锁。Sync是非公平锁。 线程2费力不讨好，但是线程2做了一件伟大的事情：他是锁升级的奠基者。）</li>
</ol>
<h2>Synchronized锁升级 - Markword转化过程（重难点）</h2>
<p>创建一个对象，此时对象里边没有hashcode，所以该对象可以使用我们的偏向锁，偏向锁不会考虑hashcode， 他会直接<strong>将自己的线程id放到我们的markword里边</strong>，不需要考虑后续的替换问题。 所以呢，一旦我们的对象主动调用了Object的hashcode方法，我们的偏向锁就自动不可用了。</p>
<p>如果我们的对象有了hashcode和分代年龄和是否为偏向锁（30位）。在轻量级锁的状态下，<strong>这30位会被复制到我们的轻量级锁线程持有者的栈帧里的lock record里边记录</strong>。与此同时，我们的<strong>对象的markword里边存放的是我们的指向轻量级锁线程持有者的栈帧的lock recod里</strong>。如果一直存在轻量级锁竞争，在未发生锁膨胀的前提下，一直会保持轻量级锁，<strong>A线程释放的时候，会将markword替换回对象的markword里边</strong>，B线程下次再从新走一遍displace mark word；</p>
<p>一旦发生了轻量级膨胀为重量级锁。前提，A线程持有锁；B线程争抢。 B线程将marikword里边A线程的指针替换成一个临时的（过渡的）重量级锁指针，为了让A线程在cas往回替换markword的时候失败。 A线程替换回markword失败后，会发起：1.初始化monitor对象；2. 将状态设置为膨胀中；3 <strong>将替换失败的 markword 放到 objectmonitr o的head属性里</strong>； 4。改变markword的锁标志为10；将markword里的 30 位设置为指向自己第一步初始化的那个monitor对象；5唤醒B线程； 6以后这个对象只能作为重量级锁；</p>
<p>Markword从未丢失。</p>
<h2>死锁 - 产生条件与避免</h2>
<p>（学院派的严谨理论）</p>
<p><strong>死锁产生的四个必要条件：</strong></p>
<ul>
<li>互斥：一个资源每次只能被一个进程使用 (资源独立)。</li>
<li>请求与保持：一个进程因请求资源而阻塞时，对已获得的资源保持不放 (不释放锁)。</li>
<li>不剥夺：进程已获得的资源，在未使用之前，不能强行剥夺 (抢夺资源)。</li>
<li>循环等待：若干进程之间形成一种头尾相接的循环等待的资源关闭 (死循环)。</li>
</ul>
<p><strong>如何避免死锁：</strong></p>
<ol>
<li>破坏” 互斥” 条件：系统里取消互斥、若资源一般不被一个进程独占使用，那么死锁是肯定不会发生的，但一般 “互斥” 条件是无法破坏的，因此，在死锁预防里主要是破坏其他三个必要条件，而不去涉及破坏 “互斥” 条件。</li>
<li>破坏 “请求和保持” 条件： 方法 1：所有的进程在开始运行之前，必须一次性的申请其在整个运行过程各种所需要的全部资源。 优点：简单易实施且安全。 缺点：因为某项资源不满足，进程无法启动，而其他已经满足了的资源也不会得到利用，严重降低了资源的利用率，造成资源浪费。 方法 2：该方法是对第一种方法的改进，允许进程只获得运行初期需要的资源，便开始运行，在运行过程中逐步释放掉分配到，已经使用完毕的资源，然后再去请求新的资源。这样的话资源的利用率会得到提高，也会减少进程的饥饿问题。</li>
<li>破坏 “不剥夺” 条件：当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时，它必须释放已经保持的所有资源，待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂的释放或者说被抢占了。</li>
<li>破坏 “循环等待” 条件：可以通过定义资源类型的线性顺序来预防，可以将每个资源编号，当一个进程占有编号为 i 的资源时，那么它下一次申请资源只能申请编号大于 i 的资源。</li>
</ol>
<p>（简易说法）</p>
<p><strong>避免死锁的几个常见方法：</strong></p>
<ul>
<li>避免一个线程同时获取多个锁。</li>
<li>避免一个线程在锁内同时占用多个资源，尽量保证每个锁只占用一个资源。</li>
<li>尝试使用定时锁，使用lock.tryLock（timeout）来替代使用内部锁机制。</li>
<li>对于数据库锁，加锁和解锁必须在一个数据库连接里（分布式数据库），否则会出现解锁失败的情况。</li>
</ul>
<h2>ObjectMonitor的五个重要属性</h2>
<ol>
<li>header ： 重量级锁保存markword的地方</li>
<li>own: 指向我们持有锁的线程；对象的markword里边也保存了指向monitor的指针；</li>
<li>_cxq 队列： 竞争队列。 A线程持有锁没有释放； B和C线程同时过来争抢锁，都被block了，此时会将B和C线程加入到 该队列。</li>
<li>EntryList队列：同步队列。A线程释放锁，B和C线程中会选定一个继承者（可以去争抢锁的这个线程），另外一个线程会被放入我们的EntryList队列里边。</li>
<li>waitset：等待队列。Object wait的线程。</li>
</ol>
<p>A线程持有锁，BC线程过来竞争失败，进入cxq – 下轮竞争会把 cxq里的线程移动到EntrylIst中。假设B线程竞争到了锁，然后B线程调用了 Object.Wait方法，这时候B线程进入waitset，并释放锁。C线程拿到了锁，然后唤醒B线程。B线程会从waitset里边出来，直接竞争锁。如果竞争失败进入cxq，继续轮回，如果竞争成功，ok了。</p>
<h2>CPU的用户态与内核态</h2>
<p>CPU 的两种工作状态：内核态（管态）和用户态（目态）。</p>
<p><strong>内核态：</strong></p>
<ol>
<li>系统中既有操作系统的程序，也有普通用户程序。为了安全性和稳定性，操作系统的程序不能随便访问，这就是内核态。即需要执行操作系统的程序就必须转换到内核态才能执行！</li>
<li>内核态可以使用计算机所有的硬件资源！</li>
</ol>
<p><strong>用户态：</strong> 不能直接使用系统资源，也不能改变 CPU 的工作状态，并且只能访问这个用户程序自己的存储空间！</p>
<p>当一个进程在执行用户自己的代码时处于用户运行态（用户态），此时特权级最低，为 3 级，是普通的用户进程运行的特权级，大部分用户直接面对的程序都是运行在用户态。Ring3 状态不能访问 Ring0 的地址空间，包括代码和数据；当一个进程因为系统调用陷入内核代码中执行时处于内核运行态（内核态），此时特权级最高，为 0 级。执行的内核代码会使用当前进程的内核栈，每个进程都有自己的内核栈。</p>
<p>用户运行一个程序，该程序创建的进程开始时运行自己的代码，处于用户态。如果要执行文件操作、网络数据发送等操作必须通过 write、send 等系统调用，这些系统调用会调用内核的代码。进程会切换到 Ring0，然后进入内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到 Ring3，回到用户态。这样，用户态的程序就不能随意操作内核地址空间，具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制，保证进程间的地址空间不会互相冲突，一个进程的操作不会修改另一个进程地址空间中的数据。</p>
<h2>用户态与内核态切换的触发条件</h2>
<p>当在系统中执行一个程序时，大部分时间是运行在用户态下的，在其需要操作系统帮助完成一些用户态自己没有特权和能力完成的操作时就会切换到内核态。</p>
<p>用户态切换到内核态的 3 种方式 </p>
<p>（1）系统调用 这是用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。例如 fork（）就是执行了一个创建新进程的系统调用。系统调用的机制是使用了操作系统为用户特别开放的一个中断来实现，如 Linux 的 int 80h 中断。 </p>
<p>（2）异常 当 cpu 在执行运行在用户态下的程序时，发生了一些没有预知的异常，这时会触发由当前运行进程切换到处理此异常的内核相关进程中，也就是切换到了内核态，如缺页异常。</p>
<p>（3）外围设备的中断 当外围设备完成用户请求的操作后，会向 CPU 发出相应的中断信号，这时 CPU 会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行，如果前面执行的指令时用户态下的程序，那么转换的过程自然就会是 由用户态到内核态的切换。如硬盘读写操作完成，系统会切换到硬盘读写的中断处理程序中执行后边的操作等。</p>
<p>这三种方式是系统在运行时由用户态切换到内核态的最主要方式，其中系统调用可以认为是用户进程主动发起的，异常和外围设备中断则是被动的。从触发方式上看，切换方式都不一样，但从最终实际完成由用户态到内核态的切换操作来看，步骤有事一样的，都相当于执行了一个中断响应的过程。系统调用实际上最终是中断机制实现的，而异常和中断的处理机制基本一致。</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/02/08/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%ba%8c-synchronized-%e5%85%a8%e8%a7%a3%e8%af%bb/">《Java并发编程的艺术》学习笔记(二) – synchronized 全解读</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/02/08/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%ba%8c-synchronized-%e5%85%a8%e8%a7%a3%e8%af%bb/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>《Java并发编程的艺术》学习笔记(一) &#8211; 并发编程初探</title>
		<link>https://www.crazyfay.com/2022/01/24/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%88%9d%e6%8e%a2/</link>
					<comments>https://www.crazyfay.com/2022/01/24/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%88%9d%e6%8e%a2/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Mon, 24 Jan 2022 10:21:23 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=166</guid>

					<description><![CDATA[<p>并发编程初探 Java天生的多线程 一个main函数就是一个JVM进程，在IDEA中可以看到共有6条线程 查看 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/01/24/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%88%9d%e6%8e%a2/">《Java并发编程的艺术》学习笔记(一) &#8211; 并发编程初探</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>并发编程初探</h1>
<h2>Java天生的多线程</h2>
<p>一个main函数就是一个JVM进程，在IDEA中可以看到共有6条线程</p>
<p>查看线程信息：jps（拿到线程的tid） + jstack（查看线程日志）</p>
<p>prio是进程中的优先级，os_prio是操作系统给线程定义的优先级，prio前加上deamo则为守护线程</p>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/6211ae1188865a265d98848f6bc2286d.png" alt="截图" /></p>
<ul>
<li>
<p><strong>[6] Monitor Ctrl-Break</strong> （跟JVM 关系不大，他是 IDEA 通过反射的方式，开启一个随着我们运行的jvm进程开启与关闭的一个监听线程。）</p>
<p>daemon prio=5 （可延迟开启）</p>
</li>
<li>
<p><strong>[5] Attach Listener </strong>（附加监听器。 简单来说，他是jdk里边一个工具类提供的jvm 进程之间通信的工具。 cmd – java -version; jvm – jstack、jmap、dump） 进程间的通信。</p>
<p>daemon prio=5 （可延迟开启）</p>
<p>开启我们这个线程的两个方式： 1. 通过jvm参数开启。-XX: StartAttachListener延迟开启： cmd – java -version –&gt; JVM 适时开启A L 线程</p>
</li>
<li>
<p><strong>[4] Signal Dispatcher</strong> （信号分发器。 我们通过cmd 发送jstack，传到了jvm进程，这时候信号分发器就要发挥作用了。）</p>
<p>daemon prio=9</p>
</li>
<li>
<p><strong>[3] Finalizer</strong> JVM 垃圾回收相关的内容。</p>
<ol>
<li>daemon prio=10 高优先级的守护线程。</li>
<li>只有当开始一轮垃圾收集的时候，才会开始调用finalize方法。</li>
<li>jvm在垃圾收集的时候，会将失去引用的对象封装到我们的 Fianlizer 对象（Reference）， 放入我们的 F-queue 队列中。由 Finalizer 线程执行inalize方法</li>
<li>Finalizer 专注垃圾收集，垃圾收集 – 并行收集，不阻碍用户线程，低优先级线程。 prio=8 他是一个守护线程啊。而且这个线程目前并没有真正的开启，不足以发生minorgc或者是 full gc</li>
</ol>
</li>
<li>
<p><strong>[2] Reference Handler</strong> （引用处理的线程。强，软，弱，虚。 -GC 有不同表现 - JVM深入分析）</p>
<p>引用处理线程-GC相关线程</p>
<p>daemon prio=10（GC很重要，优先级较高）</p>
</li>
<li>
<p><strong>[1] main</strong> 主线程</p>
<p>prio=5</p>
<p>操作系统面向的是JVM 进程，JVM 进程里面向的是 我们的main函数，。所以对于我们的操作系统如何看待我们的main函数优先级，无所谓。 只要os 给我们jvm进程足够公平的优先级就行。</p>
</li>
</ul>
<h2>线程的状态</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/a9af99ee75a683abaefcfa37ac82d984.png" alt="截图" /></p>
<blockquote>
<p>注：上图的 <code>Obejct.join()</code> 应为 <code>Thread.join()</code></p>
</blockquote>
<h2>Thread.Sleep() 和 Object.Wait()</h2>
<p>Thread.Sleep()</p>
<ul>
<li>不释放锁</li>
<li>响应中断（对中断敏感）</li>
<li>会释放CPU</li>
</ul>
<p>Obeject.wait()</p>
<ul>
<li>会释放锁</li>
<li>响应中断（对中断敏感）</li>
<li>会释放CPU，让出时间片并进入等待队列</li>
</ul>
<blockquote>
<p>注：wait(0)表示永久等待</p>
</blockquote>
<h2>Thread.join()</h2>
<p>底层调用Object.Wait()</p>
<ul>
<li>
<p>会释放锁</p>
<ul>
<li>
<p>Thread的join方法，释放的是当前调用 join方法的那个对象的锁。</p>
</li>
<li>
<pre><code class="language-java">synchronized (obj){
thread.join();//不释放锁
}
synchronized (Thread.currentThread){
thread.join();//释放锁
}</code></pre>
</li>
</ul>
</li>
<li>
<p>响应中断（对中断敏感）</p>
</li>
</ul>
<h2>线程间的通讯方式</h2>
<ol>
<li>
<p>volitate 、synchronize、lock。（都保证可见性）</p>
</li>
<li>
<p>wait、notify、await() 、 signal</p>
</li>
<li>
<p>管道输入、输出流<strong>（已过时）</strong></p>
<p>管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于，它主要用于线程之间的数据传输，而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现：PipedOutputStream、PipedInputStream、PipedReader和PipedWriter，前两种面向字节，而后两种面向字符。</p>
</li>
<li>
<p>Thread.join() ： 隐式唤醒。等待其他线程执行完成，其他线程会发送唤醒信号。</p>
</li>
<li>
<p>ThradLocal()：支持子线程集成的一种形式。</p>
</li>
<li>
<p>线程中断</p>
<p>线程中断时，sleep()会先清理中断标记，再抛出异常，导致thread.interupted = flase</p>
</li>
</ol>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/01/24/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%88%9d%e6%8e%a2/">《Java并发编程的艺术》学习笔记(一) &#8211; 并发编程初探</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/01/24/%e3%80%8ajava%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e7%9a%84%e8%89%ba%e6%9c%af%e3%80%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%88%9d%e6%8e%a2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>并发编程学习笔记</title>
		<link>https://www.crazyfay.com/2021/11/17/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/</link>
					<comments>https://www.crazyfay.com/2021/11/17/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Wed, 17 Nov 2021 13:54:37 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=194</guid>

					<description><![CDATA[<p>Java 创建线程的三种方式 先看一看在Thread类源码中的注释是怎么写的 There are two wa [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/11/17/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/">并发编程学习笔记</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h2>Java 创建线程的三种方式</h2>
<p>先看一看在<code>Thread</code>类源码中的注释是怎么写的</p>
<p>There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:</p>
<pre><code class="language-java">   class PrimeThread extends Thread {
       long minPrime;
       PrimeThread(long minPrime) {
           this.minPrime = minPrime;
       }

       public void run() {
           // compute primes larger than minPrime
            . . .
       }
   }</code></pre>
<p>The following code would then create a thread and start it running:</p>
<pre><code class="language-java">        PrimeThread p = new PrimeThread(143);
        p.start();</code></pre>
<p>The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:</p>
<pre><code class="language-java">   class PrimeRun implements Runnable {
       long minPrime;
       PrimeRun(long minPrime) {
           this.minPrime = minPrime;
       }

       public void run() {
           // compute primes larger than minPrime
            . . .
       }
   }</code></pre>
<p>The following code would then create a thread and start it running:</p>
<pre><code class="language-java">       PrimeRun p = new PrimeRun(143);
       new Thread(p).start();</code></pre>
<p>Every thread has a name for identification purposes. More than one thread may have the same name. If a name is not specified when a thread is created, a new name is generated for it.</p>
<h3>1. 继承Thread类创建线程</h3>
<pre><code class="language-java">public class lab1 {
    public static void main(String[] args) {
        //创建一个新的线程
        Runner person01 = new Runner();
        //给线程命名
        person01.setName(&quot;PERSON01&quot;);
        //启动线程

        Runner person02 = new Runner();

        person02.setName(&quot;PERSON02&quot;);

        person01.start();
        person02.start();
    }
}

class Runner extends Thread{

    @Override
    public void run() {
        Integer speed = new Random().nextInt(100);
        for (int i = 0; i &lt;= 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getName() + &quot;已前进&quot; + (i * speed) + &quot;米(&quot; + speed +&quot;米/秒)&quot;);
        }
    }
}</code></pre>
<blockquote>
<p>此时在这段Java程序中有哪几个线程?</p>
</blockquote>
<ol>
<li>主线程</li>
<li>PERSON01线程</li>
<li>PERSON02线程</li>
<li><font color =red>垃圾回收监听线程</font></li>
</ol>
<p>即Java在运行时永远不会是单一的线程</p>
<h3>2. 实现Runnable接口创建线程</h3>
<pre><code class="language-java">public class lab2 {
    public static void main(String[] args) {
        Runner02 person03 = new Runner02();
        Thread thread03 = new Thread(person03);
        thread03.setName(&quot;PERSON03&quot;);

        Thread person04 = new Thread(new Runner02());
        person04.setName(&quot;PERSON04&quot;);

        new Thread(new Runner02()).start();
        thread03.start();
        person04.start();

    }
}

class Runner02 implements Runnable{

    @Override
    public void run() {
        Integer speed = new Random().nextInt(100);
        for (int i = 0; i &lt;= 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Thread.currentThread().getName() 用于获取当前执行的线程对象
            //在 Runnable 中是无法使用 this 获取到当前线程对象的
            System.out.println(Thread.currentThread().getName() + &quot;已前进&quot; + (i * speed) + &quot;米(&quot; + speed +&quot;米/秒)&quot;);
        }
    }
}</code></pre>
<h3>3. 使用Callable和Future创建线程</h3>
<ul>
<li><code>jDK1.5</code>以后为我们专门提供了一个并发工具包<code>java.util.concurrent</code></li>
<li><code>java.util.concurrent</code>包含许多线程安全、测试良好、高性能的并发构建块。创建concurrent的目的就是要实现Collection框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块，开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。</li>
</ul>
<pre><code class="language-java">public class lab3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个线程池,里面自带三个&quot;空&quot;线程 Executors 是调度器,对线程池进行管理
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //实例化 Callable 对象
        Runner03 person04 = new Runner03();
        person04.setName(&quot;PERSON04&quot;);
        Runner03 person05 = new Runner03();
        person05.setName(&quot;PERSON05&quot;);
        Runner03 person06 = new Runner03();
        person06.setName(&quot;PERSON06&quot;);
        //将整个对象扔到线程池中, 线程池自动分配一个线程来运行person04对象的 call方法
        //Future 用于接收线程池内部call方法的返回值
        Future&lt;Integer&gt; result04 = executorService.submit(person04);
        Future&lt;Integer&gt; result05 = executorService.submit(person05);
        Future&lt;Integer&gt; result06 = executorService.submit(person06);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //关闭线程池,释放所有资源
        executorService.shutdown();
        System.out.println(&quot;PERSON04累计跑了: &quot; + result04.get() + &quot;米&quot;);
        System.out.println(&quot;PERSON05累计跑了: &quot; + result05.get() + &quot;米&quot;);
        System.out.println(&quot;PERSON06累计跑了: &quot; + result06.get() + &quot;米&quot;);
    }
}
//使用Callable 允许返回值 和 抛出异常
class Runner03 implements Callable&lt;Integer&gt;{
    private String name;

    public void setName(String name){
        this.name = name;
    }
    //实现 Callable接口可以允许我们的线程返回值或抛出异常
    @Override
    public Integer call() throws Exception {
        Integer speed = new Random().nextInt(100);
        Integer distince = 0;//总共奔跑的距离
        for (int i = 0; i &lt;= 100; i++) {
            Thread.sleep(10);
            distince = i * speed;
            System.out.println(this.name + &quot;已前进&quot; + distince + &quot;米(&quot; + speed + &quot;米/秒)&quot;);
        }
        return distince;
    }
}</code></pre>
<table>
<thead>
<tr>
<th></th>
<th>优点</th>
<th>缺点</th>
<th>使用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>继承Thread</td>
<td>编程简单</td>
<td>单继承 无法对线程组有效控制</td>
<td>不推荐使用</td>
</tr>
<tr>
<td>实现Runnable</td>
<td>面向接口编程 执行效率高</td>
<td>无法对线程组有效控制没有返回值、异常</td>
<td>简单的多线程程序</td>
</tr>
<tr>
<td>利用线程池</td>
<td>容器管理线程 允许返回值和异常</td>
<td>执行效率相对低 编程复杂</td>
<td><strong>企业级应用 推荐使用</strong></td>
</tr>
</tbody>
</table>
<h2>synchronized的使用场景</h2>
<ul>
<li>
<p>synchronized代码块 - 任意对象即可</p>
</li>
<li>
<blockquote>
<p>在堆中的对象对象头中的线程id指向当前线程,线程中的record mark记录当前对象头的信息</p>
</blockquote>
<pre><code class="language-java">class classSample{
  //锁对象
  Obeject obj = new Object();

  public void methodSample01(){
    synchronized(obj){
        //待同步代码
        . . .
    }
  }
  public void methodSample02(){
    synchronized(obj){
        //待同步代码
        . . .
    }
  }
}</code></pre>
</li>
<li>
<p>synchronized方法 - this当前对象</p>
<pre><code class="language-java">class classSample{

  public synchronized void methodSample01(){
      //待同步代码
    . . .
  }

  //给方法加锁等同于给this对象加锁
  public void methodSample02(){
    synchronized(this){
        //待同步代码
        . . .
    }
  }
}</code></pre>
</li>
<li>
<p>synchronized静态方法 - 该类的字节码对象</p>
<pre><code class="language-java">class classSample{

  public synchronized static void methodSample01(){
      //待同步代码
    . . .
  }

  //给静态方法加锁等同于给类的字节码对象加锁
  public void methodSample02(){
    synchronized(classSample.class){
        //待同步代码
        . . .
    }
  }
}</code></pre>
</li>
</ul>
<h2>线程的五种状态</h2>
<ol>
<li>新建 (new)</li>
<li>就绪 (ready)</li>
<li>运行中 (running)</li>
<li>阻塞 (blocked)</li>
<li>死亡 (dead)</li>
</ol>
<p><img decoding="async" src="../AppData/Roaming/Typora/typora-user-images/image-20220322195314329.png" alt="image-20220322195314329" /></p>
<h2>死锁</h2>
<p>死锁是在多线程情况下最严重的问题，在多线程对公共资源 （文件、数据）等进行操作时，彼此不释放自己的资源，而 去试图操作其他线程的资源，而形成交叉引用，就会产生死锁</p>
<pre><code class="language-java">public class DeadLock {
    private static String fileA = &quot;A文件&quot;;
    private static String fileB = &quot;B文件&quot;;

    public static void main(String[] args) {
        new Thread(){//线程1
            @Override
            public void run() {
                while (true) {
                    //打开文件A,线程独占
                    synchronized (fileA) {
                        System.out.println(this.getName() + &quot;: 文件A写入&quot;);
                        synchronized (fileB) {
                            System.out.println(this.getName() + &quot;: 文件B写入&quot;);
                        }
                        System.out.println(this.getName() + &quot;: 所有文件保存&quot;);
                    }
                }
            }
        }.start();

        new Thread(){//线程1
            @Override
            public void run() {
                while (true) {
                    //打开文件A,线程独占
                    synchronized (fileB) {
                        System.out.println(this.getName() + &quot;: 文件B写入&quot;);
                        synchronized (fileA) {
                            System.out.println(this.getName() + &quot;: 文件A写入&quot;);
                        }
                        System.out.println(this.getName() + &quot;: 所有文件保存&quot;);
                    }
                }
            }
        }.start();
    }
}</code></pre>
<h4>解决死锁最根本的建议是：</h4>
<ul>
<li>尽量减少公共资源的引用，用完马上释放 </li>
<li>用完马上释放公共资源</li>
<li>减少synchronized使用，采用“副本”方式替代</li>
</ul>
<h2>线程安全</h2>
<p>在拥有共享数据的多条线程并行执行的程序中，线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行，不会出现数据污染等意外情况</p>
<h4>线程安全</h4>
<ul>
<li>
<p>优点：可靠</p>
</li>
<li>
<p>缺点：执行速度慢 </p>
</li>
<li>
<p>使用建议：需要线程共享时使用</p>
</li>
</ul>
<h4>线程不安全</h4>
<ul>
<li>
<p>优点：速度快</p>
</li>
<li>
<p>缺点：可能与预期不符 </p>
</li>
<li>
<p>使用建议：在线程内部使用，无 需线程间共享</p>
</li>
</ul>
<h4>列举线程安全与不安全的类</h4>
<p><strong>StringBuffer</strong>是线程安全的，<strong>StringBuilder</strong>是线程不安全的</p>
<p><strong>HashTable</strong>是线程安全的，<strong>HashMap</strong>是线程不安全的</p>
<p><strong>Properties</strong>是线程安全的，<strong>HashSet</strong>、<strong>TreeSet</strong>是不安全的</p>
<p><strong>Vector</strong>是线程安全的，<strong>ArrayList</strong>、<strong>LinkedList</strong>是线程不安全的</p>
<h2>ThreadPool 线程池</h2>
<h4>new Thread() 的弊端</h4>
<ul>
<li>new Thread()新建对象性能差</li>
<li>线程缺乏统一管理，可能无限制的新建线程，相互竞争，严重时会占用过多系统资源导致死机或OOM</li>
</ul>
<h4>ThreadPool 线程池</h4>
<ul>
<li>重用存在的线程，减少对象创建、消亡的开销</li>
<li>线程总数可控，提高资源的利用率</li>
<li>避免过多的资源竞争，避免阻塞</li>
<li>提供额外功能，定时执行、定期执行、监控等</li>
</ul>
<h4>线程池的种类</h4>
<p>在JUC中，提供了工具类Executors（调度器）对象来创建线程池，可创建的线程池有四种：</p>
<ol>
<li>
<p>CachedThreadPool：可缓存线程池</p>
<pre><code class="language-java">public class ThreadPoolSample01 {
   public static void main(String[] args) {
       //Executors调度器对象
       //ExecutorService用于管理线程池
       //创建一个可缓存线程池
       //可缓存线程池的特点,无限大,如果线程池中没有可用的线程则创建,有空闲则利用
       ExecutorService threadPool = Executors.newCachedThreadPool();
       for (int i = 0; i <= 1000; i++) {
           final int index = i;
           threadPool.execute(new Runnable() {
               @Override
               public void run() {
                   System.out.println(Thread.currentThread().getName() + " : " + index);
               }
           });
       }
       try {
           //给线程足够的运行时间
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //threadPool.shutdown() 代表关闭线程池(等待所有线程完成)
       //threadPool.shutdownNow() 代表立即终止线程池的运行,不等待线程,不推荐使用(强制关闭)
       threadPool.shutdown();
   }
}
</code></pre>
</li>
<li>
<p>FixedThreadPool：定长线程池</p>
<pre><code class="language-java">public class ThreadPoolSample02 {
   public static void main(String[] args) {
       //创建一个定长线程池
       //定长线程池的特点,固定线程总数,空闲线程用于执行任务,如果线程都在使用
       //后续任务则处于等待状态(备选的等待算法为 FIFO(默认)和 LIFO)
       //在线程池中的线程执行任务后再执行后续的任务
       ExecutorService threadPool = Executors.newFixedThreadPool(3);
       for (int i = 0; i <= 1000; i++) {
           final int index = i;
           threadPool.execute(new Runnable() {
               @Override
               public void run() {
                   System.out.println(Thread.currentThread().getName() + " : " + index);
               }
           });
       }
       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       threadPool.shutdown();
   }
}</code></pre>
</li>
<li>
<p>SingleThreadExecutor：单线程池</p>
<pre><code class="language-java">public class ThreadPoolSample03 {
   public static void main(String[] args) {
       //Executors调度器对象
       //ExecutorService用于管理线程池
       //创建一个单线程线程池
       ExecutorService threadPool = Executors.newSingleThreadExecutor();
       for (int i = 0; i <= 1000; i++) {
           final int index = i;
           threadPool.execute(new Runnable() {
               @Override
               public void run() {
                   System.out.println(Thread.currentThread().getName() + " : " + index);
               }
           });
       }
       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       threadPool.shutdown();
   }
}</code></pre>
</li>
<li>
<p>ScheduledThreadPool：调度线程池</p>
<pre><code class="language-java">public class ThreadPoolSample04 {
   public static void main(String[] args) {
       //Executors调度器对象
       //ExecutorService用于管理线程池
       //创建一个可调度线程池
       ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
       //延迟三秒执行一次run
//        scheduledThreadPool.schedule(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("延迟3s执行");
//            }
//        }, 3, TimeUnit.SECONDS);

       //在使用可调度线程池后 Timer类就可以不再使用
       //但是在项目实际开发中两者都不会被用到,
       //因为有着更为成熟的调度框架 Quartz 或 Spring自带调度
       scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
           @Override
           public void run() {
               System.out.println(new Date() + "  延迟1s执行,每3s执行一次");
           }
       }, 1, 3, TimeUnit.SECONDS);
   }
}</code></pre>
</li>
</ol>
<h2>CountDownLatch 倒计时锁</h2>
<p>CountDownLatch倒计时锁适合&quot;总-分任务&quot;，例如多线程计算后的数据汇总，可以实现类似计数器的功能</p>
<h4>CDL执行原理</h4>
<p><img decoding="async" src="../AppData/Roaming/Typora/typora-user-images/image-20220322234437780.png" alt="image-20220322234437780" /></p>
<pre><code class="language-java">public class CountDownSample {
    private static int count = 0;
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        //CDL总数和操作数保持一直
        CountDownLatch cdl = new CountDownLatch(10000);
        for (int i = 0; i &lt;= 10000; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (CountDownSample.class) {
                        try {
                            count += index;
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            cdl.countDown();
                        }
                    }
                }
            });
        }
        try {
            //堵塞当前线程,直到cdl=0的适合再继续往下走
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
        threadPool.shutdown();
    }
}</code></pre>
<h2>Semaphore 信号量</h2>
<p>Semaphore信号量经常用于限制获取某种资源的线程数量</p>
<pre><code class="language-java">public class SemaphoreSample01 {
    public static void main(String[] args) {
        //用来装载等待的请求
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //定义5个信号量,即服务器只允许5个请求同时进行
        Semaphore semaphore = new Semaphore(5);
        for (int i = 1; i &lt;= 20 ; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取一个信号量, 占用一个&quot;使用权&quot;
                        semaphore.acquire();
                        use();
                        //执行完成后释放这个信号量, 结束服务使用
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        threadPool.shutdown();
    }

    public static void use(){
        try {
            System.out.println(new Date() + &quot; &quot; + Thread.currentThread().getName() + &quot;获得服务器使用资格&quot;);
            Thread.sleep(2000);
            System.out.println(new Date() + &quot; &quot; + Thread.currentThread().getName() + &quot;退出服务器&quot;);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<p>服务已满终止等待提示</p>
<pre><code class="language-java">public class SemaphoreSample02 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        Semaphore semaphore = new Semaphore(5);
        for (int i = 1; i &lt;= 20 ; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //尝试获取一次信号量,获取到返回true否则返回false
                        if (semaphore.tryAcquire(6, TimeUnit.SECONDS)){
                            use();
                            semaphore.release();
                        } else{
                            System.out.println(Thread.currentThread().getName() + &quot; 对不起,服务器已满,请稍后再试&quot;);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        threadPool.shutdown();
    }

    public static void use(){
        try {
            System.out.println(new Date() + &quot; &quot; + Thread.currentThread().getName() + &quot;获得服务器使用资格&quot;);
            Thread.sleep(2000);
            System.out.println(new Date() + &quot; &quot; + Thread.currentThread().getName() + &quot;退出服务器&quot;);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<h2>CyclicBarrier 循环屏障</h2>
<p>CyclicBarrier 是一个同步工具类，它允许一组线程互相等待，直到到达某个公共屏障带你。与CDL不同的是该barrier再释放等待线程后可以宠用，所以称为循环(Cyclic)屏障(Barrier)。</p>
<p><img decoding="async" src="../AppData/Roaming/Typora/typora-user-images/image-20220323002754810.png" alt="image-20220323002754810" /></p>
<pre><code class="language-java">public class CyclicBarrierSample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 1; i &lt;= 20; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    go();
                }
            });
        }
        threadPool.shutdown();
    }

    private static void go(){
        System.out.println(Thread.currentThread().getName() + &quot; 准备就绪&quot;);
        try {
            //设置屏障点,当累计五个线程都准备好后,才运行后面的代码
            cyclicBarrier.await();
            System.out.println(Thread.currentThread().getName() + &quot; 开始运行&quot;);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<h4>CyclicBarrier 应用场景</h4>
<p>适用于多线程必须同时开始的场景，抢票秒杀等等</p>
<h2>ReentrantLock 重入锁</h2>
<p>重入锁是指任意线程在获取到锁之后，再次获取该锁而不会被该锁所阻塞</p>
<p>ReentrantLock设计的目标是用来替代synchronized关键字1</p>
<table>
<thead>
<tr>
<th>特征</th>
<th>synchronized（推荐）</th>
<th>ReentrantLock</th>
</tr>
</thead>
<tbody>
<tr>
<td>底层原理</td>
<td>JVM实现</td>
<td>JDK实现</td>
</tr>
<tr>
<td>性能区别</td>
<td>低 -&gt; 高 (JDK5+)</td>
<td>高</td>
</tr>
<tr>
<td>锁的释放</td>
<td>自动释放 (编译器保证)</td>
<td>手动释放 (finally保证)</td>
</tr>
<tr>
<td>编码难度</td>
<td>简单</td>
<td>复杂</td>
</tr>
<tr>
<td>锁的粒度</td>
<td>读写不区分</td>
<td>读锁、写锁</td>
</tr>
<tr>
<td>高级功能</td>
<td>无</td>
<td>公平锁、非公平锁唤醒、Condition分组唤醒、中断等待锁</td>
</tr>
</tbody>
</table>
<pre><code class="language-java">public class ReentrantLockSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static int count = 0;//计数器
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        //调度器，JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量，用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for (int i = 0; i &lt; downTotal; i++) {
            executorService.execute(() -&gt; {
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println(&quot;下载总数：&quot; + count);
    }

    public static void add() {
        lock.lock();//上锁
        try {
            count++;
        } finally {
            lock.unlock(); //解锁，一定要放在finally里面否则会出现死锁
        }
    }
}</code></pre>
<h2>Condition 等待与唤醒</h2>
<p>在并行程序中，避免不了某些线程要预先规定好顺序执行，如：先新增再修改，先买后卖，先进后出...，对于这种场景，使用JUC中的Condition对象再适合不过了。</p>
<p>JUC中提供了Condition对象，用于让指定线程等待和唤醒，按预期顺序执行。<strong>它必须和ReentrantLock重入锁配合使用。</strong></p>
<p>Condition用于替代wait()和/notify()方法</p>
<ul>
<li>notify()只能随机唤醒等待的线程，而Condition可以唤醒指定的线程，这有利于更好的控制并发程序。</li>
</ul>
<h4>Condition核心方法</h4>
<ul>
<li>await()：阻塞当前线程，直到singal()唤醒</li>
<li>signal()：唤醒被await()的线程，从中断处继续执行</li>
<li>signalAll()：唤醒所有被await()阻塞的线程</li>
</ul>
<pre><code class="language-java">public class ConditionSample {
    public static void main(String[] args) {
        //Condition对象必须配合Lock一起使用
        ReentrantLock lock = new ReentrantLock();
        Condition c1 = lock.newCondition();//创建Condition
        Condition c2 = lock.newCondition();//创建Condition
        Condition c3 = lock.newCondition();//创建Condition

        new Thread(new Runnable() { //T1
            @Override
            public void run() {
                lock.lock();//加锁
                try {
                    c1.await();//阻塞当前线程,c1.signal的时候线程激活继续执行
                    Thread.sleep(1000);
                    System.out.println(&quot;粒粒皆辛苦&quot;);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//解锁
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();//加锁
                try {
                    c2.await();
                    Thread.sleep(1000);
                    System.out.println(&quot;谁知盘中餐&quot;);
                    c1.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//解锁
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();//加锁
                try {
                    c3.await();
                    Thread.sleep(1000);
                    System.out.println(&quot;汗滴禾下土&quot;);
                    c2.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//解锁
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    Thread.sleep(1000);
                    System.out.println(&quot;锄禾日当午&quot;);
                    c3.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}</code></pre>
<h2>Callable &amp; Future</h2>
<p>Callable 和 Runnable一样代表着任务，区别在于Callable<strong>有返回值</strong>并且<strong>可以抛出异常</strong>。</p>
<p>Future 是一个接口。它用于<strong>表示异步计算的结果</strong>。提供了检查计算是否完成的方法，以等待计算的完成，并获取计算的结果。</p>
<pre><code class="language-java">//并发计算质数
public class FutureSample {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int i = 2; i &lt;= 10000; i++) {
            Computer c = new Computer();
            c.setNum(i);
            //Future对用于计算的线程进行监听,因为计算是在其它线程中执行的,所以这个返回结果的过程是异步的
            //将c对象提交给线程池,如有空闲线程立即执行里面的call方法
            Future&lt;Boolean&gt; result = threadPool.submit(c);
            try {
                //用于获取返回值,如果线程内部的call没有执行完成,则进入等到状态,直到计算完成
                Boolean r = result.get();
                if (r){
                    System.out.println(c.getNum());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        threadPool.shutdown();
    }
}
class Computer implements Callable&lt;Boolean&gt; {

    private Integer num;

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    @Override
    public Boolean call() throws Exception {
        boolean isPrime = true;
        for (int i = 2; i &lt; num; i++) {
            if (num % i == 0){
                isPrime = false;
                break;
            }
        }
        return isPrime;
    }
}</code></pre>
<h2>JUC 并发容器</h2>
<p>ArrayList -&gt; CopyOnWriteArrayList - 写复制列表</p>
<p>HashSet -&gt; CopyOnWriteArraySet - 写复制集合</p>
<p>HashMap -&gt; ConcurrentHashMap - 分段锁映射</p>
<h4>CopyOnWriteArrayList 并发原理</h4>
<p>CopyOnWriteArrayList 通过&quot;副本&quot;解决并发问题</p>
<pre><code class="language-java">public class CopyOnWriteArrayListSample {
    public static void main(String[] args) {
        List&lt;Integer&gt; list = new CopyOnWriteArrayList &lt;&gt;();
        for (int i = 0; i &lt; 1000; i++) {
            list.add(i);
        }
        for (Integer i : list) {
            list.remove(i);
        }
        System.out.println(list);
    }
}</code></pre>
<p>CopyOnWriteArraySet 并发原理</p>
<p>与CopyOnWriteArrayList类似</p>
<h4>ConcurrentHashMap</h4>
<pre><code class="language-java">public class ReentrantLockSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static ConcurrentHashMap count = new ConcurrentHashMap();//计数器

    public static void main(String[] args) {
        //调度器，JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量，用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for (int i = 0; i &lt; downTotal; i++) {
            final Integer index = i;
            executorService.execute(() -&gt; {
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    count.put(index, index);
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println(&quot;下载总数：&quot; + count.size());
    }
}</code></pre>
<h2>Atomic 包 &amp; CAS 算法</h2>
<h4>原子性</h4>
<p>是指一个操作或者多个操作要么全部执行，且执行的过程不会被任何因素打断，要么就不执行</p>
<h4>Atomic 包</h4>
<p>专为线程安全而设计，包含多个原子操作类</p>
<p>Atomic 常用类</p>
<ul>
<li>AtomicInteger</li>
<li>AtomicIntegerArray</li>
<li>AtomicBoolean</li>
<li>AtomicLong</li>
<li>AtomicLongArray</li>
</ul>
<pre><code class="language-java">public class AtomicIntegerSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static AtomicInteger count = new AtomicInteger();//计数器

    public static void main(String[] args) {
        //调度器，JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量，用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for (int i = 0; i &lt; downTotal; i++) {
            final Integer index = i;
            executorService.execute(() -&gt; {
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println(&quot;下载总数：&quot; + count);

    }
    //线程不安全
    public static void add(){
        //count++
        count.getAndIncrement();
    }
}</code></pre>
<h4>CAS 算法</h4>
<ul>
<li>
<p>锁是用来做并发最简单的方式，当然其代价也是最高的。独占锁是 一种悲观锁，synchronized就是一种独占锁，它假设最坏的情况， 并且只有在确保其它线程不会造成干扰的情况下执行，会导致其它 所有需要锁的线程挂起，等待持有锁的线程释放锁。</p>
</li>
<li>
<p>所谓乐观锁就是，每次不加锁而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止。其中CAS（比较与交换，Compare And Swap） 是一种有名的无锁算法</p>
</li>
</ul>
<h4>Atomic的应用场景</h4>
<p>虽然基于CAS的线程安全机制很好很高效，但要说的是，并非所有线程安全都可以用这样的方法来实现， 这只适合一些粒度比较小型,如计数器这样的需求用起来才有效，否则也不会有锁的存在了</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/11/17/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/">并发编程学习笔记</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/11/17/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
