1.线程安全

线程安全的代码,本质上就是管理对状态的访问。(指共享的、可变的状态)(一个对象的状态:就是指它的数据)

线程的安全真正要做的:在不可控制的并发访问中保护数据

线程安全取决于程序中如何使用对象,而不是对象完成了什么。

  • 无论如何,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。

1.1线程安全性

“线程安全性” 关键在于你程序的 “正确性” 。(在并发环境中的隐患不会多于单线程环境下的隐患)

正确性意味着一个类与它的规约保持一致。

良好的规约定义了用于强制对象状态的不变约束,以及描述操作影响的后验条件。

  1. 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方法代码时不必作其它的协调,这个类的行为仍然是正确了,那么称这个类是线程安全的。

对于线程安全类的实例进行顺序或并发的一系列操作,多不会导致实例处于无效状态。

1.2原子性

在多线程中,进行离散操作,会造成数据的计算错误

如果用于生成序列或者对象唯一标识符,可能会导致严重的数据完整性的问题。

出现错误结果的可能性对于并发程序而言非常重要。(竞争条件

数据结果的状态衍生自它先前的状态。

1.2.1竞争条件

当计算的正确性依赖于运行时中相关的时序或者多线程的交替时,会产生竞争条件。

最常见的竞争条件:“检查再运行”。(指使用潜在的过期观察值来作决策或执行计算)

例:你观察到一些事情为真(文件x不存在),然后基于你的观察去执行一些动作(创建文件x);但但事实上,从观察到执行操作的这段时间内,观察的结果可能已经无效了(有人在此期间创建了文件x),从而引发错误。

1.2.2原子操作和复合操作

  1. 假设有操作AB,如果从执行A的线程的角度看,当其他线程执行B时,要么B全部执行完成,要么一点都没有执行,这样AB互为原子操作。
  2. 一个原子操作:该操作对于所有的操作,包括它自己,都满足前述状态。
  1. 将“检查再运行”操作(如懒汉式初始化)和 读-改-写(如自增) 操作的全部执行过程看作是复合操作。

当只向无状态类中加入唯一的状态元素,而这个状态完全被线程安全的对象所管理,那么新的类仍然是线程安全的。

1.3锁

为什么需要锁?

  1. 线程安全性的定义要求无论是多线程中的时序或交替操作,倒要保证不破坏那些不变约束。当以恶不变约束涉及多个变量时,变量间不是彼此对的,某个变量的值会制约其它几个变量的值。因此,更新一个变量时,要在同一原子操作中更新其它几个。
  1. 为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

1.3.1内部锁

  1. Java提供了强制原子性的内置锁机制:synchronized 块。
  2. synchronized 块有两部分:锁对象的引用,锁保护的代码块。
  3. synchronized 方法的锁,就是该方法所在的对象本身。
  4. 每个Java对象都可以隐式地扮演一个用于同步的锁的角色,这些内置的锁被称为内部锁或监视器锁。内部锁再Java中扮演了互斥锁的角色,意味着至多只有一个线程可以拥有锁。

1.3.2重进入

重进入:当线程在试图获得它自己占有的锁时,请求会成功。

为什么需要重进入?

  1. //如果没有重进入,下面这段代码将会死锁。
  2. //在持有了子类锁后,依旧在申请子类锁对象
  3. public class Widget {
  4. public synchronized void doSomething() {
  5. ...
  6. }
  7. }
  8. public class LoggingWidget extends Widget {
  9. public synchronized void doSomething() {
  10. System.out.println(toString() + ": calling doSomething");
  11. super.doSomething();
  12. }
  13. }

内部锁是可重进入的。

重进入意味着锁的请求是基于“每线程”,而不是基于“每调用”的。

1.4用锁来保护状态

  1. 操作**共享状态**的**复合操作**必须是**原子的**。
  2. 复合操作会再完成的运行期间占有锁。
  1. 对于每个可被多个线程访问的可变状态变量,如果所有访问它的线程在执行时都占有同一个锁,这种情况下,我们成这个变量是由这个锁保护的。
  1. 获得了与对象关联的锁**不能**阻止其它线程访问这个对象,只能阻止其它线程再获得相同的锁。
  2. 一种常见的锁规则是**在对象内部封装所有的可变状态**,通过对象的内部锁来同步任何访问可变状态的代码路径,保护它再并发访问中的安全。(例如:Vectorcollection类)。
  1. 对于每一个涉及多个变量的不变约束,需要同一个锁来保护其所有的变量。
  2. (这样才可以在一个单一的原子操作中访问或更改它们,从而保证了不变约束)

1.5活跃度和性能

  1. 如果不停的用内部锁去保护每一个变量,这种简单粗糙的方法虽然使我们重获安全性,但是代价高昂。
  1. 我们需要尽可能的去平衡简单性与并发性。请求和释放锁的操作也需要开销,所以不能将synchronized 块分解的过小,可不能过大。
  2. 有时候简单性与性能会彼此冲突,我们需要找到一个合理的平衡。
  1. 当使用锁的时候,应该要清楚块中的代码的功能,以及它的执行过程是否会很耗时,无论是做运算密集型的操作,还是执行一个可能存在潜在阻塞的操作,**如果线程长时间地占有锁,就会引起活跃度与性能风险的问题**。(例如:网络和控制台I/O