<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<Thread,T> 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>设计文档 & 用户文档</li><li>Java文档中应该注明该类使用的同步机制,应该包含下面一些内容<ul><li>是否是线程安全的?</li><li>客户回调需不需要加锁,可以加那些锁? </li><li>哪些锁保护了哪些状态?(设计文档,可以用java注释便于后续开发)</li></ul></li></ul></li></ul><hr>