• 周日. 7月 3rd, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

线程安全—相关介绍

admin

11月 28, 2021

线程安全?

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

出现线程不安全的原因是什么?

多个线程之间存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。如果根本不存在多线程,又或者一段代码根本不会与其他线程共享数据,那么 从线程安全的角度上看,程序是串行执行还是多线程执行对它来说是没有什么区别的。

Java语言中的线程安全

在Java语言中,线程安全具体是如何体现的?有哪些操作是线程安全的?按照线程安全的“安全程度”由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

1.不可变

在Java语言里面(特指JDK 5以后,即Java内存模型被修正之后的Java语言),不可变 (Immutable)的对象一定是线程安全的。Java语言中,如果多线程共享的数据是一个基本数据类型,那么只要在定义时使用final关键字修饰 它就可以保证它是不可变的如果共享数据是一个对象,由于Java语言目前暂时还没有提供值类型的 支持,那就需要对象自行保证其行为不会对其状态产生任何影响才行,不妨类比java.lang.String类的对象实例,它是一个典型的不可变对象,用户调用它的 substring()、replace()和concat()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。

保证对象行为不影响自己状态的途径有很多种,最简单的一种就是把对象里面带有状态的变量都 声明为final,这样在构造函数结束之后,它就是不可变的,例如java.lang.Integer 构造函数,它通过将内部状态变量value定义为final来保障状态不变。

在Java类库API中符合不可变要求的类型,除了上面提到的String之外,常用的还有枚举类型及 java.lang.Number的部分子类(AtomicInteger和AtomicLong是可变的),如Long和Double等数值包装类型、BigInteger和BigDecimal等大数据类型

2.绝对线程安全

 我们可以通过Java API中一个不是“绝对线程安全”的“线程安全类型”来看看这个语境里的“绝对”究竟是什么 意思。 如果说java.util.Vector是一个线程安全的容器,相信所有的Java程序员对此都不会有异议,因为它的add()、get()和size()等方法都是被synchronized修饰的,尽管这样效率不高,但保证了具备原子性、 可见性和有序性。不过,即使它所有的方法都被修饰成synchronized,也不意味着调用它的时候就永远都不再需要同步手段了。具体示例如下

 运行结果如下

很明显,尽管这里使用到的Vector的get()、remove()和size()方法都是同步的,但是在多线程的环境 中,如果不在方法调用端做额外的同步措施,使用这段代码仍然是不安全的。因为如果另一个线程恰好在错误的时间里删除了一个元素,导致序号i已经不再可用,再用i访问数组就会抛出一个 ArrayIndexOutOfBoundsException异常。如果要保证这段代码能正确执行下去,我们不得不把 removeThread和printThread的定义代码这样。假如Vector一定要做到绝对的线程安全,那就必须在它内部维护一组一致性的快照访问才行,每次对其中元素进行改动都要产生新的快照,这样要付出的时间和空间成本都是非常大的。

 

3.相对线程安全

相对线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单次的操作是线程安 全的,我们在调用的时候不需要进行额外的保障措施,但是对于一些特定顺序的连续调用,就可能需 要在调用端使用额外的同步手段来保证调用的正确性。 在Java语言中,大部分声称线程安全的类都属于这种类型,例如Vector、HashTable、Collections的 synchronizedCollection()方法包装的集合等。示例同上。

4.线程兼容

线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对 象在并发环境中可以安全地使用。我们平常说一个类不是线程安全的,通常就是指这种情况。Java类 库API中大部分的类都是线程兼容的,如与前面的Vector和HashTable相对应的集合类ArrayList和 HashMap等。

5.线程对立

线程对立是指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。由于Java 语言天生就支持多线程的特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害 的,应当尽量避免。 一个线程对立的例子是Thread类的suspend()和resume()方法。如果有两个线程同时持有一个线程对 象,一个尝试去中断线程,一个尝试去恢复线程,在并发进行的情况下,无论调用时是否进行了同 步,目标线程都存在死锁风险——假如suspend()中断的线程就是即将要执行resume()的那个线程,那就 肯定要产生死锁了。也正是这个原因,suspend()和resume()方法都已经被声明废弃了。常见的线程对立 的操作还有System.setIn()、Sytem.setOut()和System.runFinalizersOnExit()等。

希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。更多好文推荐,请关注我的微信公众号–JustJavaIt

发表评论

您的电子邮箱地址不会被公开。