Java并发编程实践

2018年05月18日

基础知识

线程的安全性

当多个线程访问某个类时,不管运行时环境采用何种调度的方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么这个类是线程安全的。

编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。

保证安全性方法有三:不共享、不可变和访问变量时使用同步。

无状态对象(即不包含任何域,也不包含任何对其他对象中域的引用)一定是线程安全的。

原子性

竞态条件(Race Condition): 由于不恰当的执行时序而出现的不正确的结果;最常见的一种竞态条件类型就是”先检查,后执行”,这种基于一种可能失效的观察结果来做出判断或者执行某种运算时大多数竞态条件的本质。

加锁机制

重入:当某一个线程试图获取一个已经由它自己持有的锁。

重入的实现方式是,为每一个锁关联一个获取计数值和一个所有者线程,当计数器值为0时,这个锁被认为是没有被任何线程持有;当线程请求一个未被持有的锁时,JVM会记下锁的所有者,并且将所获取的计数值置为1;如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减。当计数器值为0时,这个锁将被释放。

对象的共享

可见性

加锁的含义不仅仅局限于互斥行为,还包括内存的可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

volatile变量时一种比sychronized关键字;volatile变量通常用作某个操作完成、发生中断或者状态的标志等。

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

当且仅当满足以所有条件时,才应该使用volatile变量

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
  • 该变量不会与其他状态变量一起纳入不变性条件
  • 在访问变量时不需要加锁

封装能够使得对程序的正确性进行分析称为可能,并使用无意中破坏设计约束条件变得更难

发布与逸出

发布(Publist):发布一个对象是指,使对象能够在当前作用域之外的代码中使用。例如,将一个执行该对象的引用保存到其他代码可以访问到的地方,或者在某一个非私有化的方法中返回该引用,或者将该引用传递到其他类的方法中。

逸出:当某个不该发布对象被发布是称之为逸出。

线程封闭

Ad-hoc线程封闭:是指维护线程封闭性的职责完全由程序实现来承担。

栈封闭:是线程封闭的一种特例,在栈封闭当中,只能通过局部变量才能访问对象。

ThreadLocal类:这个类能够使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一个独立的副本,因此get总是返回由当前执行线程在调用set是设置的最新值。

不变性

不可变对象一定是线程安全的。

当满足下面条件时,对象才是不可变的

  • 对象创建以后其状态就不能修改
  • 对象的所有域都是final类型
  • 对象是正确创建的(在对象的创建期间,this引用没有逸出)

要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见,可以通过一线方式安全的发布对象

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

对象的组合

在设计线程安全类的过程中,需要包含以下三个基本要素

  • 找出构成对象状态的所有变量
  • 找出约束状态变量的不变性条件
  • 建立对象状态的并发访问管理策略