AtimicXXX
i++
简介
在多线程的情况下,i++不是线程安全的,那么为什么不是线程安全的呢?
i++
在底层会分为三步
内存到寄存器 – 寄存器自增 – 写回内存
- 取值
- ++操作 i+1;
- 赋值 i = i+1
当线程A执行到取值,或者++操作时,线程突然切换到线程B,B修改了值,当线程A再次拿到执行权的时候,值就会改变了。
线程安全:在并发的环境下,拿到的结果和预想的结果不一样就是线程不安全。
实例
public class Test {
//设置线程数
private static final int THREADS_CONUT = 20;
//总数
public static int count = 0;
//i++函数
public static void increase() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
//每个线程加1000次
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
//Thread.activeCount() 此方法返回活动线程的当前线程的线程组中的数量。
while (Thread.activeCount() > 2) {
//保证所有线程都能运行结束
Thread.yield();
}
System.out.println(count);
}
}
这段程序实现了20个线程,每个线程加1 1000次 所以输出应该是2000,但是输出结果有时会出现偏差,大多数情况下不是2000,而是会比他小,这里就可以看出i++不是线程安全的,那么加上volacitl呢
在public static volatile int count = 0; 给count加上volatile,结果依旧是比20000小的数,这里就可以证明volatial在并发下不是线程安全的,他不保证原子性。
所以问题还是出在自增上。
解决
- 加入synchronzied关键字,或者加入lock锁
- 使用jdk1.5后java.util.concurrent.atomic包提供了常用的原子操作
AtomicInteger
再来看上面的例子
public class Test {
private static final int THREADS_CONUT = 20;
public static AtomicInteger count = new AtomicInteger(0);
public static void increase() {
count.incrementAndGet();
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(count);
}
}
在测试10次后都是20000,输出了正确的结果,说明Atomic具有原子性。
在Java中提供了一些原子的操作类型
源码分析
首先来看他的属性,可以看到value是由volatile修饰的,保证了内存的可见性
private volatile int value;
重点分析incrementAndGet
也就是 i++操作 ****
public final int incrementAndGet() {
//无限循环
for (;;) {
//获取当前值
int current = get();
//将+1赋给next
int next = current + 1;
//如果比较和next相同,进行set,否则就是被其他线程修改过,重新获取当前值
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这里采用的是CAS操作, incrementAndGet() 在一个无限循环体内,不断尝试将一个比自己大1的新值付给自己,如果失败说明在当前值已经被其他线程修改过,于是便再次进入循环下一次操作,直到成功为止。
而compareAndSet()调用的是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。
这里不懂的可以先去看看乐观锁的介绍
compareAndSwapInt()方法,即Unsafe类的CAS操作。
这里不懂的可以先去看看乐观锁的介绍
Atomic就是volatile的使用场景,也是CAS的使用场景