1.线程安全的定义
如何给线程安全下一个包含有用信息的定义?
当多个线程同时访问同一对象时,如果不用考虑这些线程在运行时环境下如何调度及交替执行,也不需要额外的同步操作,或在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。 --《JAVA并发编程实战》
这个定义严谨而又具有可操纵性,Java中的线程安全是指多线程同时操作一个共享变量时无需采取其他的加锁或同步互斥机制,而又能保证始终正确的行为。追求彻底的线程安全往往是繁琐的,需要付出巨大的代价,有时候是不现实的,而且往往也没有必要,这里就可以参考《深入理解JVM》一书中提出的线程“安全程度”概念。
2.线程安全程序等级
- 不可变
- 绝对线程安全
- 相对线程安全
- 线程兼容
- 线程对立
2.1不可变对象
我们知道要编写线程安全的代码核心问题是对状态访问操作进行管理,尤其是“共享的”、“可变的”的状态。如果一个对象创建后就不可变,在面对多线程的同时访问时,自然就是线程安全的。但是这里要注意,不可变对象在初始化过程中,也就是在构造函数中,此对象的this引用不要逸出,否则其他线程可能会看到构造的尚未完全的对象,造成线程安全问题。
不可变对象的定义:
- 对象创建以后就不能被修改
- 对象的所有域都是final的
- 对象是正确创建的(初始化过程中,this引用没有逸出) 换句话说如果一个对象是不可变的,那么在多线程无其他额外操作的情况下,对该对象的状态访问绝对就是线程安全的。在Java中可以讲基本数据类型用final修饰,引用数据类型中的String、Integer、Double等类型是典型的不可变对象。(注意String的replace,subString 等方法,调用后是返回的新的String对象)
String类中value数组使用final修饰,在构造函数中初始化完成后,不能被修改。
String类的concat方法,可以看到执行concat方法是在不可变的Value上返回了一个new String()
2.2 绝对线程安全
不管运行环境如何,调用者都不需要额外的同步措施,往往需要付出非常高昂的,不切实际的代价。Java Api中很多标注自己是线程安全的类,绝大多数都不是“绝对线程安全的”。例如Vector是一个线程安全的,集合类,但是一个线程在对vector进行遍历时,另一个线程对vector内元素进行修改,将会触发Vector的fail-fast机制,导致错误发生。
2.3 相对线程安全
上面讲到了“绝对线程安全”往往不切实际,而相对线程安全才是我们经常讲的线程安全。 相对线程安全是指:保证对对象的单次操作是线程安全的,我们对对象的调用无需额外的措施,但对于一些特定顺序的连续调用,就需要在调用段使用额外的同步操作来保证调用的正确性。 在Java类库中有很多“相对线程安全”的例子: 1.集合类:List下的同步Vector,写时复制技术的Vector,以及Collections.syn下的各种装饰类 2.Map:实现了Map接口的HashTable、以及使用分段锁技术的ConcurrentHashMap
2.4 线程兼容
是指对象本事不是线程安全的,但是调用发可以使用恰当的同步手段来保证对象在并发环境下可以安全的使用。我们平常说的线程不安全通常就是指此种情况。 例如:HashMap、ArraryList,HashSet等都是线程不安全的,在多线程并发环境下使用,必须考虑加锁等机制。
2.5 线程对立
此种情况较为特殊,是指不管调用端采取了何种同步措施,都无法在多线程环境下使用的代码。 一个经典例子时Thread类的resume() 和 suspend(),如果两个线程同时持有一个线程对象,一个尝试去resume,另一个尝试去Suspend,无论是否同步,目标线程都有死锁风险。
3.如何保证线程安全
我们在上面分析了何为线程安全,和线程安全程度概念。有哪些方法可以帮助我们实现线程安全?
- 互斥同步
- 非阻塞同步
- 无同步方案
3.1互斥同步
互斥同步在Java中的实现主要是两个加锁操作(Synchronized和ReentrantLock)和 扩展AQS同步器的一些工具类(CountDownLatch,Semaphore,CyclicBarrier)。 Synchronized在JDK早期版中性能不如ReentrantLock,现在主流版本的Synchronized性能在轻量锁,偏向锁,自适应锁自旋的优化后并不比ReentrantLock差.这两种锁都支持重入操作,Synchronized相比ReentrantLock的优势是使用更简单,无需手动释放锁。而ReentrantLock在支持锁重入的情况下,在加锁过程中支持中断和超时,相比synchronized更不容易发生死锁,而且还支持Condition条件变量来实现线程间的通信操作。注意ReentrantLock不要忘记在finally块中释放锁。
3.2 非阻塞同步
主要实现是Java中的CAS比较并交换操作,和相关的原子类,AtmoticInteger,AtmoticReference等。
3.3 无同步方案
ThreadLocal,volatile,final等。