JAVA并发实战学习笔记——3,4章~

<h3>JAVA并发实战学习笔记</h3>
<hr>
<h4>第三章 对象的共享</h4>
<ul>
<li>
<strong>失效数据</strong>:
<ul>
<li>java程序实际运行中会出现<strong>①程序执行顺序对打乱;②数据对其它线程不可见</strong>——两种情况</li>
<li>上述两种情况导致在缺乏同步的程序中出现<strong>失效数据</strong>这一现象,且“失效”这一情况不确定性很大,因为可能出现可能没出现。</li>
<li>
JVM中没有规定对于64位变量如:long, double 的读写操作必须是原子的,因此不同步的情况下读取该类数据可能得到的值无意义(低32位和高32位没有一起形成完整的数字)
<ul>
<li>商用的JVM一般会让64位变量的读取原子化。</li>
</ul>
</li>
</ul>
</li>
<li>
使用<strong>volatile</strong>修饰变量可以保证变量的可见性,但不会保证互斥性,是比内置锁更弱的同步机制。
<ul>
<li>
在以下两种情况下可以使用volatle保证同步。但是过度依赖volatile会使得代码脆弱、可读性差。
<ul>
<li>1.只有一个线程对volatile变量实行写操作;</li>
<li>2.变量不需要与其它状态变量共同参与不变约束。</li>
</ul>
</li>
<li>加锁操作兼保证原子性和可见性</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
<p><strong>发布</strong>(将变量的引用保存到其它代码可以访问到的地方)与逸出(不该发布的变量被发布了):</p>
<ul>
<li>
发布的几种方式:
<ul>
<li>public static A</li>
<li>某个对象A的引用在另一个对象B中,发布B则间接地发布了A</li>
<li>将对象传递给外部方法</li>
<li>发布内部类对象A,A中隐含地包含了外部类对象B</li>
</ul>
</li>
</ul>
</li>
<li>
<p>逸出</p>
<ul>
<li>在构造函数中启动了新线程(该对象的内部线程类),使得this指针在实例没有建好时便被其它线程共享了。</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
<strong>线程封闭</strong>:线程封闭是一类编程的方法,使得各个线程中的对象是不相互共享的,如:JDBC中的Connection对象
<ul>
<li>Ad-hoc线程封闭:尽量少用,十分脆弱</li>
<li>
栈封闭:局部变量由于在运行中是处于线程栈的局部变量表和操作数栈中,所以局部变量无论是否是线程安全,只要其引用不发布出去,都是线程安全的。
<ul>
<li>以能用局部变量就别用全局的变量,全局变量容易引起并发问题。</li>
</ul>
</li>
<li>
ThreadLocal类线程封闭:可将ThreadLocal<t> t 看成是Map&lt;Thread,T&gt; t,保存了每个线程到T对象的一份副本,t.get()得到的是initialValue()设定的值。
<ul>
<li>副本保存在线程中,随着线程的技术被垃圾回收</li>
<li>引入了类之间的耦合性,小心使用</li>
<li>ThreadLocal类应用场景示例:移植单线程程序到多线程环境</li>
</ul>
</t></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
<strong>不变性</strong>
<ul>
<li>
满足下列三个条件的对象满足“<strong>对象不可变</strong>”
<ul>
<li>创建好后,不提供更改其状态的方法</li>
<li>所有的域都是 final型</li>
<li>对象是正确创建的</li>
</ul>
</li>
<li>“<strong>对象引用不可变</strong>”————相比于<strong>对象不可变</strong>,对象的内容可以改变,但是对象地址无法改变(即用final修饰的变量)</li>
<li>
不可变对象为一系列操作提供弱原子性
<ul>
<li>只要将参数传入方法,在该方法内就有指向域的引用,其它的线程修改了原对象的域,也不会影响到该方法对原域的访问。</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
<strong>安全发布</strong>————确保可以不受JAVA不可见性的影响,得到发布的最新的对象
<ul>
<li>
<strong>不可变对象</strong>安全发布的方式
<ul>
<li>final域具有特殊的初始化安全性保证,在初始化的时候即使没有同步,也可以保证其可见性。 </li>
</ul>
</li>
<li>
<strong>可变对象</strong>安全发布的方式
<ul>
<li>静态初始化对象(标有static的对象初始化代码是在类初始化阶段执行的,JVM自带线程安全特性)</li>
<li>将对象的引用保存在volatile类型的域中,或者AtomicReference对象中</li>
<li>对象引用存入final类型域中</li>
<li>将对象引用存入某个由锁保护的域中</li>
</ul>
</li>
</ul>
</li>
<li>
安全访问
<ul>
<li>
事实不可变对象和不可变对象
<ul>
<li>任意访问</li>
</ul>
</li>
<li>
可变对象
<ul>
<li>访问时需要同步机制,对象需要是线程安全,或者访问前先获得某个锁</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h4>第四章 对象的组合</h4>
<hr>
<ul>
<li>
如何建立线程安全的类
<ul>
<li>
<strong>1.</strong>收集同步需求————候选范围为对象的域,包括对象中包含的基本类型变量,以及域对象,和域对象内的域
<ul>
<li>
不变性条件
<ul>
<li>单个变量,即变量的值需要在其合法范围内</li>
<li>多个变量,即多个变量的值之间需要满足某些约束</li>
<li>访问同一个不变性条件中任何一个变量,都需要获得同一个锁,以确保对操作不会破坏不变性条件</li>
</ul>
</li>
<li>
后验条件
<ul>
<li>变量的值转换需要满足的约束</li>
</ul>
</li>
<li>不变性条件和后验条件约束了对象的哪些状态和状态转换是有效的</li>
</ul>
</li>
<li><strong>2.</strong>先验条件————即依赖状态的操作</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
<p><strong>实例封闭</strong>————使封装的数据被封闭在另一个对象中,被封闭的对象不超出其作用域????</p>
<ul>
<li>
<p>一般知识</p>
<ul>
<li>将对实例内封装的对象的访问限制在对象的方法上,以确保线程在访问数据是总能持有正确的锁. </li>
<li>
含有线程不安全的内部对象的线程安全类示例:
<ul>
<li>Collections.synchronized***()方法通过装饰器模式将容器封装在一个线程安全的对象中</li>
</ul>
</li>
</ul>
<pre><code>
public class PersonSet{

private final Set<person> myset = new HashSet<person>;
public synchronized void addPerson(Person p){
myset.add(p);
}

public synchronized boolean containsPerson(Person p){
return myset.contains(p);
}
}
</person></person></code></pre>
<ul>
<li>
疑惑之处:
<ul>
<li>
不超出作用域?如何保证方法有合适的返回值呢?觉得很奇怪。
<ul>
<li>
<font color="#ff0000" size="5" face="黑体">安全发布状态变量的三个条件</font>
<ul>
<li>1. 状态变量是线程安全的</li>
<li>2. 变量不存在不变性约束</li>
<li>3. 在方法中不包含使得该变量进入不合法状态的操作</li>
</ul>
</li>
</ul>
</li>
<li>
而且使用锁同步,和不使对象溢出有什么联系呢?我觉得这就是两个独立的东西,放在一起凑成一节,让人困惑
<ul>
<li>猜测:要实现非线程封闭的线程安全类,封闭对象是第一步,限制访问方法并使访问方法同步是第二步。</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
Java监视器模式(即私有的所对象,而非内置锁)
<ul>
<li>好处是不会让外部的方法得到该对象的锁</li>
<li>若容器内的对象是非线程安全的,可以每次发布该对象的时候都深度复制</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
线程安全性的<strong>委托</strong>
<ul>
<li>将线程安全性委托给单个/多个(<strong>彼此独立的对象,且所有的方法中都不包含无效状态转换操作</strong>)线程安全的状态</li>
<li>若类中包含不符合上一条要求的,包含多个有不</li>
<li>条件约束的状态,实现线程安全性需要加锁机制</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
在现有的线程安全类中<strong>添加新功能</strong>
<ul>
<li>在并发中,能用现成的线程安全类就尽量用;若现成的类满足不了需求,则可能选择添加新功能</li>
<li>
四种方法:
<ul>
<li>改源码 ————不现实</li>
<li>
扩展基础类 ———— 在自己的子类新添加的方法中使用内置锁——从这里来看,子类和父类中使用的内置锁应该是同一个,都是真对实例而言的。
<ul>
<li>缺点:破坏了类的封装性</li>
</ul>
</li>
<li>
客户端加锁机制 ———— 了解基础类对象使用的是什么锁,在客户端代码中使用相同的锁
<ul>
<li>缺点:破坏了同步策略的封装性 </li>
</ul>
</li>
<li>
组合模式 ————使用装饰器模式将基础类如:list等封装在内部,将list的方法包装一层,使用装饰器类的内置锁。
<ul>
<li>
优点
<ul>
<li>不会破坏封装性</li>
<li>健壮性更强</li>
<li>即使容器类不是线程安全的,也可以借此实现线程安全</li>
</ul>
</li>
<li>
缺点
<ul>
<li>多加一层锁,效率下降</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>
文档!!!文档!!!
<ul>
<li>设计文档 &amp; 用户文档</li>
<li>
Java文档中应该注明该类使用的同步机制,应该包含下面一些内容
<ul>
<li>是否是线程安全的?</li>
<li>客户回调需不需要加锁,可以加那些锁? </li>
<li>哪些锁保护了哪些状态?(设计文档,可以用java注释便于后续开发)</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>

posted @ 2018-07-20 12:31  学编程的兔子  阅读(87)  评论(0编辑  收藏  举报