十、线程安全
本篇主要侧重原理,与Java语言层面的线程安全手段关系不大。
一、Java语言中的线程安全
这里的讨论的线程安全,限定于多个线程之间存在共享数据访问这个前提,因为如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度来看,程序是串行执行还是多线程执行对它来说是完全没有区别的。按照线程安全的“安全程度”由强至弱的来排序,可将Java语言中对共享数据的操作分为以下5类:
1.不可变
不可变的对象一定是线程安全的。如果共享数据是基本类型,则只需用final修饰即可保证不可变。如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响。方式有很多种,最简单的就是把对象中带有状态的变量都声明为final。
2.绝对线程安全
一个类不管运行时环境如何,调用者都不需要任何额外的同步措施,即视为绝对线程安全。在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。比如java.util.Vector,即使所有的方法都被修饰为同步,也不以为着调用它的时候永远都不需要同步手段了。
3.相对线程安全
相对线程安全就是我们通常意义上说的线程安全,它需要保证对这个对象单独的操作是线程安全的。Java语言中,大部分线程安全类都属于这种类型。
4.线程兼容
对象本身不是线程安全的,但是可以通过在调用端正确使用同步手段来保证对象在并发环境下可以安全使用。平时说的一个类不是线程安全的,通常指这种情况。
5.线程对立
无论调用端是否采取同步措施,都无法在多线程环境中并发使用的代码。如Thread类的suspend()和resume()就存在这种风险,所以被废弃了。
二、线程安全的实现方式
1.互斥同步
同步指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用。互斥是实现同步的一种手段,临界区(Critical Section)、Synchronized、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。
2.非阻塞同步
互斥同步主要问题是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。从处理问题的方式上看,互斥同步属于一种悲观的并发策略,总认为只要不去做正确的同步措施,那就肯定会出问题,无论共享数据是否真的出现竞争,它都要进行加锁、用户态和心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒操作等。
随着硬件指令集的发展,多了一个选择:基于冲突检测的乐观并发策略,即先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据要争用,产生了冲突,那就采取其他的补偿措施,这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。