您的位置:首页 > 编程语言 > Java开发

Java语言中的线程安全

2015-09-21 19:26 423 查看
“线程安全”这个名称,相信稍有经验的程序员都会听说过,甚至在代码编写和走查的时候可能还会经常挂在嘴边,但是如何找到一个不太拗口的概念定义线程安全却不是一件容易的事,笔者在Google中尝试搜索它的概念,找到的类似于是“如果一个对象可以安全地被多个线程同时使用,那么就是线程安全的”这样的定义——并不能说它不正确,但是人们无法从中获取到任何有用的信息。

笔者认为《Java Concurrency In Practice》的作者Brian Goetz对线程安全有一个比较恰当的定义:“当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行其他的协调操作,调用这个对象的行为都可以得到正确的结果,那这个对象就是线程安全的”。

这个定义比较严谨,它要求线程安全的代码都必须具备一个特征:代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程的问题,更无需自己采取措施来保证多线程的正确调用。这点听起来简单,但其实并不容易做到,在大多数场景中,我们都会将这个定义弱化一些,如果把调用这个对象的行为限定为单次调用,这个定义的其他描述也能够成立的话,我们就可以称它是线程安全的了,为什么要弱化这个定义,现在暂且放下,稍后再详细讨论。

Java语言中的线程安全

我们已经知道了线程安全的一个抽象定义,那接下来就讨论一下Java语言中的线程安全是如何具体体现的?有哪些操作是线程安全的?我们这里讨论的线程安全,就限定于共享数据访问这个前提,因为如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度来看,程序是串行还是多线程执行对它来说完全是没有区别的。

为了更加深入理解线程安全,在这里我们可以不把线程安全当做一个非真即假的二元排他选项来看,按照线程安全的“安全程度”由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类:不可变绝对线程安全相对线程安全线程兼容线程对立

1、不可变

在Java语言中(特指JDK1.5以后,即Java内存模型被修正之后的Java语言),不可变(Immutable)的对象一定是线程安全的,无论是对象的方法的实现还是方法的调用者,都不需要再采取任何的线程安全保障措施,我们知道final关键字带来的可见性,只要一个对象被正确地构建出来(没有this引用逃逸的情况),那其外部的可见状态永远也不会改变,永远也不会看到它在多个线程之中处于不一致的状态。“不可变”带来的安全性是最简单和最纯粹的。

Java语言中,如果共享数据是一个基本数据类型,那么只要定义时使用final关键字修饰它就可以保证它是不可变的。如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行(想想java.lang.String,我们调用它的substring(),replace()和concat()这些方法都不会影响它原来的值,只会返回一个新的字符串对象)。

保证一个对象的行为不影响自己的状态有多种途径,其中最简单的方法就是把对象中带有状态的变量全都声明为final,这样在构造函数结束之后,它就是不可变的。在Java API中符合不可变要求的类型,除了java.lang.String还有枚举类型,以及java.lang.Number的部分子类,如Long和Double等数值包装类型,BigInteger和BigDecimal等大数据类型;但同为Number的子类AtomicInteger和AtomicLong则并非不可变。

2、绝对线程安全

绝对的线程安全完全满足Brian Geotz给出的线程安全的定义,这个定义其实是很严格的,一个类要达到“不管运行环境如何,调用者都不需要任何额外的同步措施”通常需要付出很大的,有时甚至根本不切合实际的代价。在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。我们
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: