java中的线程安全(Threadsafe)及其四种策略
- 线程安全:
线程之间存在“竞争条件”,作用于同一个mutable数据上的多个线程,彼此之间存在对该数据的访问竞争并导致interleaving,导致post-condition可能被违反,这是不安全的。
线程安全:ADT或方法在多线程中要执行正确。
要做到:不违反spec,保持RI。与多少处理器,如何调度线程无关,不需要在spec中强制要求client满足某种“线程安全”的义务。
例如:Iterator就不是线程安全的,因为你不能在迭代遍历的时候改变collection。
保证线程安全,有四种策略:
(1)限制数据共享(Confinement)
(2)共享不可变数据(Immutability)
(3)共享线程安全的可变数据(Threadsafe data type)
(4)同步机制(Synchronization)
下面依次进行介绍:
-
Confinement-限制数据共享
将可变数据限制在单一线程内部,避免竞争。
不允许任何线程直接读写该数据。
核心思想: 线程之间不共享mutable数据类型
避免全局变量:例如我们之前的单例模式(Singleton Design Pattern)就存在风险:
-
Immutability:
使用不可变数据类型和不可变引用,避免多线程之间的race condition。
关于不可变的更强定义:
(1)无mutator方法
(2)所有属性均为private和final的
(3)没有表示泄露
(4)表示中没有对可变类型的变化,甚至有益的变化也不允许
不允许子类重写方法:最简单的做法实在类前加上关键final
-
Using Threadsafe Data Types: 如果必须要用mutable的数据类型在多线程之间共享数据,要使用线程安全的数据类型。 在JDK中的类,文档中明确指明了是否threadsafe 一般来说, JDK同时提供两个相同功能的类,一个是threadsafe,另一个不是。 原因:threadsafe的类一般性能上受影响 。 集合类都是线程不安全的。 因此Java API提供了进一步的decorator: 进行包装(Wrap)
要注意的是:
在使用synchronizedMap(hashMap)之后,不要再把参数hashMap共享给其他线程,不要保留别名,一定要彻底销毁。
即使在线程安全的集合类上,使用iterator也是不安全的,除非使用lock机制(后续介绍)。
-
Locks and Synchronization:
同步与锁,最复杂也最具有价值的threadsafe策略。
前三种策略的核心思想:
避免共享->即使共享,也只能读/不可写(immutable)->即使可写,共享的可写数据
也应自己具备在多线程之间协调的能力,即使用线程安全的mutable ADT。
而很多时候,无法满足上述三个条件。
因此采用同步与锁策略:
程序员来负责多线程之间对mutable数据的共享操作,通过“同步” 策略,避免多线程同时访问数据。
一个锁就是一个线程在一个时间段对数据的占用。使用锁机制,获得对数据的独家mutation权,其他线程被阻塞,不得访问。
lock的两个操作:
获得与释放。
可以使用synchronized方法,或在方法中加入代码块:
后者需要显式的给出lock,且不一定非要是this,能提供更加精细的lock粒度。
任何共享的mutable变量/对象必须被lock保护
涉及到多个mutable变量的时候,必须被同一个lock所保护。
同步机制给性能带来很大影响,除非必要,否则不要用。Java中很多mutable 的类型都不是threadsafe就是这个原因。
尽可能减小lock的范围。
线程安全的声明:
Concurrency argument:
threadsafe by monitor pattern:all accesses to rep are guarded by this object's lock。
上一篇: throws与throw
下一篇: throw 与throws