- 线程不安全的场景:
public class CASTest {
int i = 0;
public static void main(String[] args) throws InterruptedException {
CASTest casTest = new CASTest();
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
casTest.i++;
}
}).start();
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
casTest.i++;
}
}).start();
Thread.sleep(3000);
System.out.println(casTest.i);
}
}
运行结果每次都不一样,但总是小于200
定义了一个变量,但是这个变量线程不安全,当A线程读取读取了变量的值,缓存在自己的内存之中,没有写回主内存的时候,恰好B线程又读取了i变量的值,结果两次加一,但是实际只加了一次
这也证明i++这个操作并不是原子性操作
- 解决办法:
public class CASTest {
AtomicInteger i = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
CASTest casTest = new CASTest();
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
casTest.i.getAndIncrement();
}
}).start();
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
casTest.i.getAndIncrement();
;
}
}).start();
Thread.sleep(3000);
System.out.println(casTest.i.get());
}
}
每次运行结果都是:200
这样就可以保证这个变量是线程安全的
-
AtomicInteger源码实现主要有两点:
volatile关键字保证可见性和有序性
cas操作保证原子性 -
源码:
如何保存变量的值private volatile int value;
getAndIncrement()方法如何执行
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * 1 通过cas保证变量的值没有被修改 * 2 如果被修改就不断重试,直到成功为止 */ public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
其实在这种并发频繁的场景下不适合使用cas(乐观锁)来处理线程安全,知识为了验证AtomicInteger的作用
因为这里的不断重试造成了cpu的消耗,对于并发频繁的场景,可以直接使用synchronized(悲观锁)关键字处理