线程安全

2/10/2017来源:ASP.NET技巧人气:931

因为线程共享相同的内存地址空间,且并发地运行,它们可能访问或修改其他线程正在使用的变量。这是十分方便的,因为它使得数据共享相对于其他的线程间通讯机制都更加简单。但是这其中也存在着巨大的风险:当数据意外改变时,线程可能会出现混乱。允许多线程访问和修改相同的变量,给顺序编程模型引入了一些非顺序因素,这可能会造成混乱,并且难以发现错误的原因。为了使多线程程序的行为可预见,访问共享的变量必须经过合理的协调,这样线程才不会互相干扰。

非线程安全的序列生成器

public class UnsafeSequence { PRivate int value; public int getNext() { return value++; } }

线程安全的序列生成器

public class Sequence { private int value; public synchronized int getNext() { return value++; } }

编写线程安全的代码,本质上就是管理对状态(state)的访问,而且通常都是共享的,可变的状态

通俗地说,一个对象的状态就是它的数据,存储在状态变量中。

所谓共享,是指一个变量可以被多个线程访问;所谓可变,是指变量的值在其生命周期内可以改变。线程安全好像是关于代码的,但真正要做的是在不可控制的并发访问中保护数据。

一个对象是否应该是线程安全的取决于它是否被多个线程访问。线程安全的这个性质取决与程序中如何使用对象,而不是对象完成了什么。保证对象的线程安全性需要使用同步来协调对其可变状态的访问;若是做不到这一点,就会导致脏数据和其他不可预测的后果。

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

线程安全的定义

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

线程安全的类封装了任何必要的同步,因此客户不需要自己提供。

无状态对象永远是线程安全的。

为了确保线程安全,“检查再运行”操作(如惰性初始化)和读-改-写操作(如自增)必须是原子操作。

为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

不可变性

创建后状态不能被修改的对象叫做不可变对象。不可变对象天生就是线程安全的。它们的常量(域)是在构造函数中创建的。

不可变状态永远是线程安全的

无论是java语言规范还是Java存储模型,都没有关于不可变性的正式定义,但是不可变性并不简单地等于将对象中的所有域都声明为final类型,所有域都是final类型的对象仍然可以是可变的,因为final域可以获得一个可变对象的引用。

只有满足如下状态,一个对象才是不可变的 - 它的状态不能在创建后再被修改 - 所有域都是final类型,并且 - 它被正确创建 正如“将所有的域声明为私有的,除非它们需要更高的可见性”一样,“将所有的域声明为final类型,除非它们是可变的”,也是一条良好的实践。

参考文献

戈茨. JAVA并发编程实践[M]. 电子工业出版社, 2007.