概念
线程安全性
多个线程访问同一对象时,(主调程序不做同步措施),该对象也能表现出正确的行为
- 无状态的对象一定是线程安全的
- 无状态的对象引入一个线程安全的状态时,依然是线程安全的
Java多线程内存模型
JMM规定了jvm有主内存(Main Memory)和工作内存(Working Memory) ,主内存存放程序中所有的类实例、静态数据等变量,是多个线程共享的,而工作内存存放的是该线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每个线程私有的其他线程不能访问,每个线程对变量的操作都是以先从主内存将其拷贝到工作内存再对其进行操作的方式进行,多个线程之间不能直接互相传递数据通信,只能通过共享变量来进行
竞态条件
- 例子:先检查后执行(延迟初始化)
- 基于一个可能失效的的观测结果而做出判断或者计算
- 出错分析: 在观测结果后,执行操作前,观测结果可能失效,系统的状态可能发生了改变,此时继续执行计算就可能导致系统出错。
- 竞态条件不一定会导致错误,还需要不巧当的执行时序。
原子性
访问同一个状态的所有操作,要么都执行完,都么都不执行,所有操作作为一个整体,其中无法插入其他操作,而这些操作有可能会更改系统的状态,引发竞态,而原子性则确保了复合操作之间的整体性,避免出现不恰当的执行时序,从而保证线程安全性。
- Java.util.concurrent.atoimc中有一些原子变量,用于在数值和对象引用上的原子状态变换
- 除了原子变量提供的原子操作外,还可以使用锁来保证操作的原子性
- 状态一致性:在拥有多个状态的类中,并不是简单的使用多个原子类管理状态就可以了,还需要将不同状态的设置都集中在单个原子操作中才可以,因为程序的执行可能依赖多个状态,这些状态同步更新才能保证系统正常。
可见性
当一个线程修改对象状态时,其他线程能够看到发生的状态变化。
- 重排序:影响执行时序
- Violatile:只确保可见性
- 发布:使对象能够在当前作用域之外的代码中使用 安全发布
逸出:某个不该被发布的对象被发布
保证线程安全
锁
Java内置锁-同步代码块(Synchronized Block):线程进入同步代码块获得锁,退出代码块释放锁,最多只有一个线程能持有这种锁。
- 可重入:线程试图获取一个已经由他自己持有的锁。例如:子类重写父类同步方法时,调用父类的同步方法
- 保证可见性和原子性
线程封闭
不共享数据:仅在单线程内访问数据,不需要同步,当某个对象被封闭在一个线程中,这种用法将自动实现线程安全性,即使该对象本身不是线程安全的。例如:Swing 和 JDBC连接池
- ad-hoc
- 栈封闭:将对象放到局部变量中,局部变量被存放在线程各自的栈中,其他线程无法访问
- ThreadLocal
不变对象
某个对象在其创建后其状态就不能修改,则这个对象就是不可变对象。不可变对象一定是线程安全的。
满足以下条件,对象才是不可变的:
- 对象创建以后其状态就不能修改
- 对象的所有域都是final域
- 对象是正确创建的(对象创建期间,this引用没有逸出)
保存在不可变对象中的程序状态仍然可以更新,即通过一个保存新状态的实例来“替换”原有的不可变对象
当需要对一组相关数据操作时(复合操作),可以考虑创建一个不可变的类来包含这些数据,通过将不同的数据和操作封装在不可变对象中,(这些数据也将作为final不可变属性),由于整个类的不可变性,保证了不同操作的整体性,在某种程度上提供了一种弱原子性,如果此时再与Violatile配合使用,可以达到同步效果
实例封闭
- 将数据对象封闭在一个线程安全的类中,并且保证它不会逸出自己的作用域,那么访问这个数据对象的所有路径都将由包装类控制,只要包装类实现同步,整体就是线程安全的。把对象放到线程安全的容器中,如Vector,HashTable,BlockingQueue等
- Java监视器模式
- 和线程封闭区别:数据是否共享
委托
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换(多个变量之间有相互制约性,对整体不变性增加了额外的限制),那么可以将线程安全性委托给底层的状态变量,类本身不用做额外同步。
如果一个类A的状态变量由B组成,要把A实现成线程安全,则保证B整体是线程安全即可。
多个底层状态变量,如上例中B里面的变量,如果不可变(final等修饰),则可以在上层类(A)中发布,不会影响线程安全,如果是可变的,那么上层类 (A)中一定不能发布(Public)该变量。整体上的效果就是,保证这些状态变量对外界来说是不可修改的,因此可以避免出现状态不一致,参见不变对象。
和实例封闭区别:委托是调用者不管事,底层组件实现线程安全,实例封闭是调用者来同步,而底层不管事
- 代理
如果一个类本身是线程安全的,我们需要对其进行扩充时,新增一个原子操作,这个时候使用代理模式,在代理里面将各种操作同步起来,然后转发给下级,这样不管下级类中的同步策略如何变化,都能保证该代理是线程安全的
To be continued。