Atomic相关类和CAS机制
原子操作
public class TestCollection {
public static void main(String[] args) throws Exception {
final TestCollection test = new TestCollection();
for (int i = 0; i < 60000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
test.add();
}
}).start();
}
Thread.sleep(3000);
System.out.println(test.i);
}
volatile int i=0;//使用volatile,保证了可见性
public void add() {
i++;
}
}
结果:(结果不确定,<=60000)
59995
由于i++不是原子操作,使用 javap -v -p xxx.class 反编译,可以看i++操作的字节码指令,如下图红框中的4行:
i++分为3个步骤:
1、获取i的值
2、将i进行+1操作
3、把i+1得到的结果赋值到i
两个线程t1,t2,当t1线程执行步骤1后,t2线程开始执行步骤1,即:t1获取到的i=0,t2获取到的i=0,然后t1、t2进行步骤2操作,最后进行步骤3操作,导致,赋值的结果都是1。而不是我们期待的结果2。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。
将整个操作视为一个整体,资源在该次操作中保持一致,这是原子性的核心特征。
可以使用synchronized关键字,reentrantLock(),atomicInteger()等等,把i++变为原子操作,实现原子操作。
CAS(Compare and swap)
Compare and swap 比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。
CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先对旧值进行比较,若没有发送变化,才交换成新值,发生了变化则不交换。
JAVA中的sun.misc.Unsafe类,提供了compareAndSwapInt()和compareAndSwapLong()等几个方法,实现CAS。
如果CAS操作失败了,进行自旋(for循环)
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestCollection {
public static void main(String[] args) throws Exception {
final TestCollection test = new TestCollection();
for (int i = 0; i < 60000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
test.add();
}
}).start();
}
Thread.sleep(6000);
System.out.println(test.i);
}
volatile int i = 0;
// 保存i字段的偏移量
private static long valueOffset;
private static Unsafe unsafe = null;
static {
// unsafe = Unsafe.getUnsafe();//是不行的,会报错
// 使用反射,获取unsafe
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
// 获取到i的偏移量
Field fieldi = TestCollection.class.getDeclaredField("i");
valueOffset = unsafe.objectFieldOffset(fieldi);
} catch (Exception e) {
e.printStackTrace();
}
}
public void add() {
// i++;
for (;;) {
// 参数:那个对象,字段偏移量。。。。获取到i对象
int current = unsafe.getIntVolatile(this, valueOffset);
// 参数:那个对象,那个字段,旧值,新值 。。。。。修改i对象
if (unsafe.compareAndSwapInt(this, valueOffset, current,
current + 1)) {
// 修改成功,则跳出循环
break;
}
}
}
}
运行结果:
60000(每次都是60000)
J.U.C包内的原子操作封装类
CAS的三个问题
1、循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗。
2、仅针对单个变量的操作,不能用于多个变量来实现原子操作。
3、ABA问题。
ABA问题
线程安全概念
竞态条件:如果程序运行顺序的改变会影响最终结果,就说存在竞态条件。
大多数竞态条件的本质,就是基于某种可能失效的观察结果,来做出判断或执行某个计算。
临界区:存在竞态条件的代码区域叫临界区。
共享资源
只有当多个线程更新共享资源(线程内:线程共享内存区域,线程外:DB等)时,才会发生竞态条件,可能会出现线程安全问题。
栈封闭,不会再线程之间共享的变量,都是线程安全的。
局部对象引用本身不共享,但是引用的对象存储在共享堆中。如果方法内创建的对象,只是在方法中传递,并且不对其他线程可用,那么也是线程安全的。
不可变的共享对象,保证对象在线程间共享时不会被修改,从而实现线程安全。
实例被创建,value变量就不能在被修改,这就是不可变性。
使用ThreadLocal时,相当于不同的线程操作的是不同的资源,所以不存在线程安全问题。