当多个线程去访问某个类时,如果类会表现出我们预期出现的行为,那么可以称这个类是线程安全的。
count++
。它本身包含三个操作,读取、修改、写入,多线程时,由于线程执行的时序不同,有可能导致两个线程执行后count只加了1,而原有的目标确实希望每次执行都加1;instance == null
成立,然后新建了两个对象,而原有目标是希望这个对象永远只有一个;public MyObj getInstance(){ if (instance == null){ instance = new MyObj(); } return instance } 复制代码 解决方式是:当前线程在操作这段代码时,其它线程不能对进行操作 常见方案:
synchronized的可重入性 当线程要去获取它自己已经持有的锁是会成功的,这样的锁是可重入的,synchronized是可重入的 class Paxi { public synchronized void sayHello(){ System.out.println("hello"); } } class MyClass extends Paxi{ public synchronized void dosomething(){ System.out.println("do thing .."); super.sayHello(); System.out.println("over"); } } 复制代码 它的输出为 do thing .. hello over 复制代码
常见方案:
注意:Volatile并不能保证操作的原子性,比如count++
操作同样有风险,它仅保证读取时返回最新的值。使用的好处在于访问Volatile变量并不会执行加锁操作,也就不会阻塞线程。
读取-修改-写入
视情况而定。
综上,DVT的安全交给了‘locations’,它本身是线程安全的,DVT本身虽没有任何显示的同步,也是线程安全。这种情况下,就是DVT的线程安全实际是委托给了‘locations’,整个DVT表现出了线程安全。
依赖的增加则无法保证线程安全
public class NumberRange{ private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i){ //先检查后执行,存在隐患 if (i>upper.get(i)){ throw new IllegalArgumentException('can not ..'); } lower.set(i); } public void setUpper(int i){ //先检查后执行,存在隐患 if(i<lower.get(i)){ throw new IllegalArgumentException('can not ..'); } upper.set(i); } } 复制代码
setLower和setUpper都是‘先检查后执行’的操作,但是没有足够的加锁机制保证操作的原子性。假设原始范围是(0,10),一个线程调用 setLower(5),一个设置setUpper(4)错误的执行时序将可能导致结果为(5,4)
假设需要扩展的功能为 ‘没有就添加’。