竞态条件和临界区
多线程访问相同资源,对资源进行写操作时,对执行顺序有要求。
public class Demo {
public int i = 0;
public void incr() {
i++;
}
}
临界区:incr方法内部就是临界区域,关键部分代码的多线程并发执行,会对执行结果产生影响。
竞态条件:可能发生在临界区内的特殊田间。多线程执行incr方法中i++关键代码时,产生了竞态条件。
共享资源:
如果一段代码是线程安全的,则不包含竞态条件,只有当多个线程更新共享资源时,才会产生竞态条件。
栈封闭状态下,不会在线程之间进行数据共享,所以是线程安全的。
局部对象引用是不共享的,但是引用的对象是存放在堆内存当中的。如果对象引用是在方法内创建的,只在方法中进行传递,并且对其他线程是不可用的,那它也是线程安全的。
不可变对象:
如果创建的是不可变的共享对象,对象在线程间共享时不会被修改,不可变对象也是线程安全的。
java的原子性操作:
概念可参考数据库的原子性操作。
原子操作可以是一个步骤也可以是多个步骤,但是执行顺序不可改变,也不可以切割执行,而是将整个操作过程视为一个整体的操作。
竞态条件下,线程是不安全的,需要将操作转换为原子操作,才能保证线程安全。
经典案例:多线程循环执行i++;
方法:锁、循环CAS。
CAS机制:
Compare and Swap 比较和交换,处理器提供基本内存操作的原子性保证。
Cas操作需要两个数值,操作前的值和操作后的值,在操作期间,先比较原值是否发生改变,如果发生改变,则不替换,没有改变时,才将新值写入,替换原有旧值。
Java中,sun.misc.Unsafe类,提供了compareAndSwapInt等几个方法,实现CAS操作,保证了原子性。
compareAndSwapInt方法可能会失败。类似数据库的乐观锁操作。在真正的业务中,该方式并不常用,使用不方便。
简单的基本类型的操作也可以通过AtomicInteger等进行操作。
AtomicInteger内部就是反射,调用sun.misc.Unsafe的操作。
Cas的问题:
1、cas+循环:不适合多个属性的原子操作;
2、容易导致CPU占用100%;
3、aba问题,有线程不知道数据被修改了 ;