深入理解JVM:线程安全与锁优化

来自Wikioe
Eijux讨论 | 贡献2020年10月27日 (二) 01:36的版本 →‎线程安全
跳到导航 跳到搜索


 

线程安全

《Java并发编程实战(Java Concurrency In Practice)》的作者Brian Goetz对“线程安全”的定义:

“当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。”

Java语言中的线程安全

按照线程安全的“安全程度”由强至弱来排序,可以将Java语言中各种操作共享的数据分为以下五类:“不可变”、“绝对线程安全”、“相对线程安全”、“线程兼容”和“线程对立”。

不可变

在Java语言里面(特指JDK 5以后,即Java内存模型被修正之后的Java语言),不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再进行任何线程安全保障措施。


不可变数据:

  1. 如果共享数据是一个基本数据类型:只要在定义时使用final关键字修饰它就可以保证它是不可变的。
  2. 如果共享数据是一个对象:需要对象自行保证其行为不会对其状态产生任何影响才行。
    可以类比java.lang.String类的对象实例,它是一个典型的不可变对象,用户调用它的substring()、replace()和concat()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。


Java类库API中不可变类型:
除了上面提到的String之外,常用的还有枚举类型及java.lang.Number的部分子类,如Long和Double等数值包装类型BigIntegerBigDecimal等大数据类型。

  • (但同为Number子类型的原子类AtomicInteger和AtomicLong则是可变的)

绝对线程安全

绝对线程安全:“不管运行时环境如何,调用者都不需要任何额外的同步措施”。

  • 在Java API中标注线程安全的类,大多数都不是绝对的线程安全。(使用这些类时,在方法调用端可能仍需要做额外的同步措施)

相对线程安全

相对线程安全,就是通常意义上所讲的“线程安全”:

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

线程兼容

线程兼容,即通常意义的“线程不安全”,指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。

线程对立

线程对立是指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。

  • 由于Java语言天生就支持多线程的特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。
  • 常见的线程对立的操作:Thread类的suspend()和resume()方法,还有System.setIn()、Sytem.setOut()和System.runFinalizersOnExit()等。

线程安全的实现方法

锁优化

自旋锁与自适应自旋

锁消除

锁粗化

轻量级锁

偏向锁