第2章 线程安全性

来源:互联网 发布:貂蝉离间数据 编辑:程序博客网 时间:2024/05/15 21:39

第2章 线程安全性

  • 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。
    • 对象的状态,从非正式的意义上说,指存储在状态变量(例如实例或静态域)中的数据,对象的状态中包含了任何可能影响其外部可见行为的数据
    • “共享”意味着变量可以由多个线程同时访问
    • “可变”意味着变量的值在其生命周期内可以发生变化。
  • 一个对象是否需要是线程安全的,取决于它是否被多个线程访问。这指的是在程序中访问对象的方式,而不是对象要实现的功能。
  • 避免一个状态变量因并发访问产生错误,有三种办法:
    • 不在线程间共享该变量
    • 将该变量设为不可变的
    • 使用同步访问该变量

2.1 什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
理解:在线程安全的类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。
无状态对象一定是线程安全的。


2.2原子性

理解:通常指一个操作或一组操作是不可分割的

2.2.1竞态条件

所谓竞态条件,指某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。
最常见的竞态条件类型就是“先检查-后执行”和“读取-修改-写入”操作,即通过一个可能失效的观察结果来决定下一步的动作。”先检查-后执行”的典型场景是延迟初始化,“读取-修改-写入”的典型场景是递增一个计数器。这些操作也称为复合操作,即包含了一组必须以原子方式执行的操作以确保线程安全性。

2.3加锁机制

要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

2.3.1内置锁

java提供了一种内置的锁机制来支持原子性:同步代码块。同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。
每个java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

2.3.2重入

内置锁是可重入的,这意味着如果一个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。这说明,java中获取锁的操作的粒度是“线程”而不是“调用”。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。

2.4用锁来保护状态

对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一把锁,在这种情况下,我们称状态变量是由这个锁保护的。
对象的内置锁与其状态之间没有内在的关联。当获取一个对象内置的锁时,并不能阻止其他线程访问该对象,某个线程在获得对象的锁之后,只能阻止其他线程获得同一个锁。
每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。
对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

2.5活跃性与性能

通常,在简单性与性能之间存在着相互制约因素,当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性(这可能破坏安全性)。
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁。

0 0