浅谈多线程安全
2015-05-30孙明鹏胡飞胡长春
孙明鹏 胡飞 胡长春
【摘要】多线程编程的基本概念存在于研究与开发实验室中已有数十年之久。多线程的真正价值在语言设计的理论基础和实战细节上是显而易见的,利用现代处理器和多处理器及其特点,多线程可具有更好的性能。多线程的应用最重要的问题是线程安全,未解决和解决不完全的线程安全问题,其导致的错误很难发现且很难被调试,并且线程所导致的错误所造成的损失是巨大且无可估量的。
【关键词】多线程 编程 安全
一、什么是线程安全
当你的程序所在的进程之中有多个线程在同时运行,而这些线程可能会同时运行一段代码或者会同时访问一个对象,如果每次运行这段代码或对对象访问之后,所得到的结果和单线程情况下运行结果一样,而且其他的变量的值也和预期保持一致,我们就认为是线程安全的。也就是说,当多个线程同时运行同一段代码时不会造成资源的冲突,不会产生错误的结果就是线程安全的。如过有一段线程安全的代码,它在多个线程中使用时不需要做同步处理;而线程不安全的代码在多线程环境中使用必须要做同步处理,否则将会出现不可预期的后果。
二、线程不安全所造成的影响
在不进行数据保护操作时,当一段代码或者对象可能被多个线程访问,而且访问顺序不能确定,那么一旦这段代码或者对象处于有条件的线程安全,线程兼容,线程对立这一分类,就可能出现多个线程先后更改数据,从而造成得到的数据不一致,或者数据出现污染,更严重的时候会出现脏数据,甚至程序崩溃。线程不安全是可怕的,一方面它对于程序编写人员来说是一场灾难,因为线程不安全导致的错误会被程序复杂的逻辑掩盖,它所造成的错误与逻辑不严谨所造成的错误基本上相似,这就为除错造成了困难,很多时候需要重新审视整个代码,甚至很长时间无法发现问题所在。另一方面它对于程序的使用者造成的损失基本无法估计,在当下的数据时代,任何数据的不一致,数据的污染都会给一个公司和个人造成无法挽回的影响。
三、线程不安全的分类
线程的安全程度并没有一个统一的分类,在乔希·布洛赫所给出线程分类描述,线程安全性分为五类,分别是:不可变、线程安全、有条件线程安全、线程兼容和线程对立。当然,若在明确的明白下线程安全特性的情况下,无论是否使用这种分类系统都没有关系。乔希·布洛赫所提出的分类方法也有局限性,各个分类之间的界限并不是绝对的明朗,但是作为对线程安全的分类,确是简单明了的。下面就各个类别进行详细的说明:
1,不可变的
不可变的对象一定是线程安全的,因为其不可变的特性,它永远也不需要进行额外的同步操作。只要一个不可变的对象在构建时候是正确的,那么永远也不会看到它处于不一致的状态。再代码中,基本类型,基本数值类,字符常量都是不可变的。
2,线程安全的
所有线程安全的代码或者对象都具有上面所提到的线程安全的属性,也就是说,这段代码或对象在多线程环境下,被多个线程访问,不管这些线程的访问顺序是何种顺序,所有访问的线程都不需要进行额外的同步工作。这种线程安全的要求是严格的,满足这种要求的和满足不可变類型的所有代码或者对象都是绝对的线程安全。
3,有条件的线程安全
有条件的线程安全,指的是对于这段代码或者对象仅仅在一些操作访问顺序不同需要额外的同步操作。最显著的有条件的线程安全是在遍历一些返回迭代器对象的时候,如果在遍历这些对象的同时存在另一个或多个线程进行了添加或移除内部元素的操作,必然会出现迭代器的失效。为了保证在某一线程在执行遍历操作的时候该对象不会被其他线程访问,应通过相应的手段,阻止其他线程做出导致该对象的迭代器失效的操作。进行迭代的线程应该确保它是独占性的访问该对象,从而保证了遍历的完整性。
4,线程兼容的
线程兼容的代码或者对象不是线程安全的,但是可以通过正常使用同步而在多线程环境下安全的使用。
5,线程对立的
线程对立是指一段代码或者对象在运行中,一旦存在多进程对它进行操作,总是不能保证线程安全的,这种不能保证不管是否进行了同步的操作。线程对立是一种罕见且特殊的情况,例如当一段代码或对象中的静态数据被修改之后,被修改的数据可能会影响到其他代码或者其他对象的行为时,这种时候就会出现线程对立。
四、防范线程不安全影响的措施
1.对于可能会运行在多线程环境下的代码和对象的编写人员,应尽量的保证该代码在多线程环境每次运行结果和单线程情况下运行结果一致,而且其他的变量和值也能和预期结果保持一致。这种情况从根本上保证了线程安全。
2.对于可能会运行在多线程环境下的代码和对象的编写人员,在编写代码和对象的时候,应注意辨别是否能在多线程下正确运行,根据上文提出的线程安全的分类,对于有条件的线程安全的,线程兼容的,线程对立的应该给以相应的标记,或在文档之中详细的写出说明。保证该代码或者对象的潜在使用者能够通过查看注释或文档,清楚明白的知道该代码或对象在多线程环境下的运行状态。
3.对于某段代码或者某个对象的使用者,在使用这段代码或对象之前,应该清楚的知道该代码和对象是否是线程安全的,并且应该清楚的知道接下来使用这段代码或对象的运行环境是否为多线程环境,根据不同的情况决定是否需要进行线程间的同步,或者用互斥锁等方法使得线程能够有序的访问该代码或对象。
4.对于某段代码或者某个对象的使用者,如果确定为多线程环境,且确定接下来使用的代码或对象有可能会被多个线程访问,并且无法确定其是否是线程安全的时候,应尽量通过多种方式确定其线程安全与否,这些方式包括联系作者或者通过自己的测试,以及查看源代码。
5.对于软件设计者来说,当程序不能按照设计运行,应当在检测逻辑错误的同时,检测是否因为线程不安全而导致的错误是必要的。依次检查所使用别人代码或别人提供的对象是否存在线程安全问题是最为合适的。
6.对于已经发现因为线程安全问题导致错误的软件使用者,应及时停止使用该软件,及时联系软件厂商,要求修改。
参考文献:
1 Lubomir F.Bic,Alan C.Shaw.操作系统原理[M].北京:清华大学出版社,2005
2 安德鲁斯.多线程,并行与分布式程序设计基础(影印版)[M].北京:高等教育出版社,2002
3 Bruce Eckel.Thinking in JAVA[M].London:Prentice Hall,2006