一个对象是否需要是线程安全的取决于它是否被多个线程访问。
当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错。解决办法:
- 不在线程之间共享该变量
- 将状态变量修改为不可变的
- 在访问状态变量时使用同步机制
完全由线程安全类构造的程序也不一定是线程安全的,线程安全类中也可以包含非线程安全的类
一、什么是线程安全性
线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的。(因为线程安全类中封装了必要的同步代码)
一个无状态的类是线程安全的。无状态类是指不包含任何域或也没有引用其它类的域。一次特定计算的瞬间状态,会唯一存在本地变量中。
二、原子性
1、竞争条件:由于不恰当的执行时许而出现了不正确的运行结果,当计算的正确性取决于交叉执行的顺序时
错误原因:基于一个可能失效的观察结果进行下一步操作
例1:读取-修改-写入 操作: 当两个线程交叉执行并同时读取到了相同的值,addOne操作便造成了偏差-1的结果。
1 public class Test {
2 private int x = 0;
3 public void addOne(){
4 x++;
5 }
6 }
例2:先检测后执行 操作:没有进行同步操作的单件模式
2、复合操作
概念:包含一组必须以原子方式执行的操作以保证线程的安全性
原子操作是线程安全的 vs 竞争条件是不安全的
在无状态的类中加入一个确保线程安全的状态可以保证该类仍未线程安全的,但加入多个线程安全状态时没有办法保证。
三、加锁机制
1、内置锁:java提供了强制原子性的内置锁机制:synchronized 块,每个java对象都可作为锁,称为内置锁或监视锁
- synchronized(lock){访问或修改共享变量}
- synchronized方法为保证整个方法体为原子的,lock为this
- 静态synchronized方法以Class对象为锁
最多只有一个对象持有该锁,进入同步代码块获得锁,出代码块释放锁
synchronized特性不能被继承,即覆盖方法需要也写synchronized关键字
2、重入
内置锁是可以重入的,即某个线程可以获得它已经持有的锁。(例同步子类方法调用父类方法super.method()会请求它已经有的锁)
JVM提供锁内线程计数器予以保证正确释放锁
四、用锁来保护对象
锁使受保护的代码以串行形式执行。
对于可能被多个线程访问的状态变量,访问它的时候都要加锁,不只是在写入时加锁。
每一个共享的或可变的状态都应该由一个锁保护
锁机制仅仅阻止了其他线程获得同一个锁,而不能防止访问对象
即使每个方法都使用了同步机制,但由他们呢构成的复合操作不一定保证并发正确。
例
1 public add(E e){
2 if(!vector.contains(e))
3 vector.add(e);
4 }
五、活跃性与性能
如果对整个services方法加锁,方法内有某个计算耗时较长时,那么将严重影响效率
修改代码后将绿色箭头指的大计算量部分重同步代码中取出,可以并发操作,改进性能