文档章节

【撸码师读书笔记】Java并发编程实战

一介码夫_Hum
 一介码夫_Hum
发布于 2017/03/27 17:05
字数 2919
阅读 16
收藏 1

第一部分 基础知识

 

第三章 对象的共享

 

3.4 不变性

3.4.1 Final域

  1. final类型的域是不能修改的(但如果final域所应用的对象是可变的,那么这些被引用的对象是可以修改的),在Java内存模型中,final域还有着特殊的语义。final域能确保初始化的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象时无须同步;
  2. 即使对象时可变的,通过将对象的某些域声明为final类型,仍然可以简化对状态的判断,因此限制对象的可变性也就相当于限制了该对象可能的状态集合
  3. 除非需要更高的可见性,否则ying应将所有的域都声明为siyo私有域”[ EJ Item 12 ]是一个良好的编程习惯,“除非需要某个域是可变的,否则应将其声明为final域”也是一个良好的编程习惯;

3.4.2 示例:使用Volatile类型来发布不可变对象

  1. volatile不能保证被修饰的对象是线程安全的,只是通知其他线程该对象最新更新值,使该对象具有线程间可见性。
  2. //TODO

 

============================================================

 

3.5 安全发布

3.5.1不正确的发布:正确的对象被破坏

由于未能够正确的发布对象,导致可能出现类似多个线程看到不同状态的共享对象;

 

3.5.2 不可变对象与初始化安全性

由于不可变对象是一种非常重要的对象,因此Java内存模型为不可变对象的共享提供了一种特殊的初始化安全性保证。

即使某个对象的引用对其他线程是可见的,并不意味着对象状态对于使用该对象的线程来说一定是可见的。为了确保对象状态能够呈现出一致的视图,就必须使用同步

任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。

 

3.5.3 安全发布的常用模式

要安全的房补一个对象,对象的引用与对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全的发布:

  • 在静态初始化函数中初始化一个对象引用。
  • 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
  • 将对象的引用保存到某个正确构造对象的final类型域中。
  • 将对象的引用保存到一个由锁保护的域中。

线程安全容器内部的同步意味着,在将对象放入到某个容器,例如Vector或SynchronizedList时,将满足上述最后一条(将对象的引用保存到一个由锁保护的域中)需求。线程安全库中的容器类提供了以下的安全发布保证

  • 通过一个键或者值放入Hashtable、SynchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)。
  • 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、SynchronizedList或者SynchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
  • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

 

3.5.4 事实不可变对象

如果对象从技术上是可变的,但其状态在发布后不会再改变,那么把这种对象称为“事实不可变对象(Effectively Immutable Object)”。

3.5.5 可变对象

如果对象在构造后可以修改,那么安全发布只能够确保“发布当时”状态的可见性。对于可变对象,不见在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保后续操作的可见性。

对象的发布需要取决于它的可变性:

  • 不可变对象可以通过任意机制来发布。
  • 事实不可变对象必须通过安全方式来发布。
  • 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

3.5.6 安全地共享对象

在并发程序中使用或共享对象时,可以使用一些实用的策略,包括:

  1. 线程封闭。 线程封闭的对象只能由一个线程拥有,对象被封闭在线程中,并且只能由这个线程修改。
  2. 只读共享。 在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,担任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。

  3. 程安全共享。 线程的安全对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。

  4. 保护对象。 被保护的对象只能通过持有特定的所来访问。保护对象包括封线程安全对象中的对象,以及已发布的并且由某个特定所保护的对象。

 

 

第四章 对象的组合

 

4.1设计线程安全类

4.1.1收集同步需求

//TODO

 

 

第五章 基础构建模块

 委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可

 Java平台类库包含了丰富的并发基础构建模块,例如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类(Synchronizer)

5.1 同步容器类

  同步容器类包括Vector和Hashtable,两者是早起JDK的一部分,此外包括JDK1.2中添加的一些功能相似的类。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。

5.1.1 同步容器类问题

  同步容器类都是线程安全的,但某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代、跳转以及条件运算等;

5.1.2 迭代器与ConcurrentModificationException

  无论在直接迭代还是在Java5.0引入的for-each循环语法中,对容器类进行迭代的标准方式都是使用Iterator。在设计同步容器类的迭代器时并没有考虑到并发修改问题,并且它们表现出的行为是“及时失败”(fail-fast)的。这意味着,当它们发现容器在迭代过程中被修改时,就会抛出ConcurrentModificationException异常。

  这种“及时失败”采用的实现方式是,将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么hasNext或next将抛出ConcurrentModificationException。

 如果不想再迭代期间对容器加锁,那么一种替代方法就是“克隆”容器,并在副本上进行迭代。由于副本被封闭在线程内,因此其他线程不会在迭代期间对其进行修改,这样避免抛出ConcurrentModificationException(在克隆过程中仍然需要对容器加锁)。克隆容器时存在显著性能开销,因此这种方式的好坏取决于多种因素,如容器大小,在每个容器上进行的操作,迭代操作相对于容器其他操作的调用频率,以及在响应时间和吞吐量等方面的需求。

5.1.3 隐藏迭代器

public class HiddenIterator{
  private final Set<Inteter> set = new HashSet<Integer>();

  public synchronized void add( Integer i ){set.add(i);}
  public synchronized void remove( Integer  i ){set.remove(i);}

  public void addTenThings(){
   Random r = new Random();
   for ( int i = 0 ; i < 10 ; i++ ){
       add(r.nextInt());
    }
     System.out.println("DEBUG: added ten elements to " + set );
  }
}

  编译器将字符串的连接操作转换为调用StringBuilder.append(Object),而这个方法又会调用容器的toString方法,标准容器的toString方法将迭代容器,并在每个元素上调用toString来生成容器内容的格式化标识。

  ↑ ↑ ↑ ↑ ↑在调试代码和日志代码中通常会忽视这个问题 ↑ ↑ ↑ ↑ ↑。

 容器的hashCode和equals等方法也会间接地执行迭代操作,当容器作为另一个容器的元素或键值是,就会出现这种情况。同样,containsAll、removeAll和retainAll等方法,以及把容器作为参数的构造函数,都会对容器进行迭代。所有这些间接的迭代器操作都可能抛出ConcurrentModificationException

 

5.2 并发容器

5.2.1 ConcurrentHashMap

同步容器类在执行每个操作期间都持有一个锁。

  ConcurrentHashMap与HashMap一样,也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。其使用一种粒度更细的加锁机制来实现更大程度的共享-“分段锁(Lock Striping)”。在这种机制中,任意数量的读取线程可以并发访地访问Map,执行读取操作的线程和执行写入操作的线程可以并发地访问Map,并且一定数量的写入线程可以并发的修改Map

  ConcurrentHashMap与其它并发容器一起增强了同步容器类:

  •    不会抛出ConcurrentModificationException,因此不需要在迭代过程中对容器加锁
  •    返回的迭代器具有弱一致性(Weakly Consistent),并非“及时失败”。
  •    弱一致性特性决定了容器可以容忍并发修改,当创建迭代器时会遍历已有的元素,并可以(但是不保证)在迭代器被构造后将修改操作反映给容器。

   一些需要在整个Map上进行计算的方法,如size和isEmpty,这些方法在语义上被减弱了以反映容器的并发特性。size其实是一个估计值,因此允许size返回一个近似值而不是一个精确值。权衡之后,其实size、isEmpty等方法在高并发环境中的作用很小,因为它们的返回值总是在不断变化。

  只有当应用程序需要加锁Map以进行独占访问时,才应该考虑放弃使用ConcurrentHashMap。

5.2.2 额外的原子Map操作

5.2.3 CopyOnWriteArrayList

  CopyOnWriteArrayList用于替换同步List,在某些情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制。(类似地,CopyOnWriteArraySet的作用是替换同步Set)。

  “写入时复制(Copy-On_Write)”容器的线程安全在于,只要正确地发布一个事实不可变对象,那么访问该对象时就需要进一步同步。在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性

  每次修改容器时都会复制底层数组,这需要一定的开销,特别是容器的规模较大时。仅当迭代操作远远多于修改操作时,才应该选择使用“写入时复制”容器

 

© 著作权归作者所有

一介码夫_Hum
粉丝 25
博文 122
码字总数 30761
作品 0
海淀
其他
私信 提问
读书笔记之《Java并发编程的艺术》-并发编程容器和框架(重要)

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
724
1
读书笔记之《Java并发编程的艺术》-并发编程基础

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
4K
8
读书笔记之《Java并发编程的艺术》-线程池和Executor的子孙们

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
756
1
读书笔记之《Java并发编程的艺术》-java中的锁

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
409
0
Android--面试中遇到的问题总结(三)

《Android 开发工程师面试指南 LearningNotes 》,作者是陶程,由梁观全贡献部分。大家可以去知乎关注这两位用心的少年。这份指南包含了大部分Android开发的基础、进阶知识,不仅可以帮助准备...

sealin
2017/02/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

PostgreSQL 11.3 locking

rudi
今天
5
0
Mybatis Plus sql注入器

一、继承AbstractMethod /** * @author beth * @data 2019-10-23 20:39 */public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMap......

一个yuanbeth
今天
10
1
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
今天
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
今天
8
0
队列-链式(c/c++实现)

队列是在线性表功能稍作修改形成的,在生活中排队是不能插队的吧,先排队先得到对待,慢来得排在最后面,这样来就形成了”先进先出“的队列。作用就是通过伟大的程序员来实现算法解决现实生活...

白客C
今天
81
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部