Talk is cheap, show u the code:
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
private int seriaNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
}
public int getSeriaNumber() {
return seriaNumber++;
}
}
输出结果是什么?为什么?
注意: 不一定产生上述相同结果,也不一定产生线程安全问题,线程安全问题是随机发生的,可能需要多次运行
此时已经产生线程安全问题,十个线程未能均匀的将serialNumber加到9
可见性问题?那加上volatile试试
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
private volatile int seriaNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
}
public int getSeriaNumber() {
return seriaNumber++;
}
}
注意: 不一定产生上述相同结果,也不一定产生线程安全问题,线程安全问题是随机发生的,可能需要多次运行
问题原因分析:
i++的原子性问题
比如有:
int i = 10;
i = i++;
System.out.print("i = " + i); // i = 10
i = i++操作实际上在内存中分为三个步骤
int temp = i;
i = i + 1;
i = temp;
当线程1执行 i = i + 1时,将i的值更新为0,可能此时线程2 已经读了temp的值为 0,可能在执行i = i + 1 后,又将 i 的值更新为 0
解决办法:
利用Atomic变量
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
// private volatile int seriaNumber = 0;
private AtomicInteger seriaNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
}
public int getSeriaNumber() {
// return seriaNumber++;
return seriaNumber.getAndIncrement();
}
}
多次运行发现结果不会再出现线程安全问题
原子变量:JDK1.5提供的
- 利用volatile保证内存的可见性
- CAS(Compare And Swap)算法保证变量的原子性
CAS算法是硬件对于并发操作的共享变量的支持,含有三个操作数:
- 内存值 v
- 预估值 A
- 更新值 B
当且仅当 V == A 时,将V的值更新为B,否则不做任何操作
JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的实现。如下:
```
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作。
valueOffset为变量值在内存中的偏移地址,unsafe就是通过偏移地址来得到数据的原值的。
value当前值,使用volatile修饰,保证多线程环境下看见的是同一个。
我们就以AtomicInteger的addAndGet()方法来做说明,先看源代码:
public final int addAndGet(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta) + delta; }
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;
}
内部调用unsafe的getAndAddInt方法,在getAndAddInt方法中主要是看compareAndSwapInt方法:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
吐血,写不下去了,写这篇学习笔记的时候还是有很多疑惑,先结掉,日后再修改。天呐,路漫漫其修远兮。。