先运行下面一段程序:
package concurrent;
class AtomicDemo implements Runnable {
private int serialNumber = 0;
public int getSerialNumber() {
return serialNumber++;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getSerialNumber());
}
}
public class TestAtomic {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for(int i=0;i<10;i++)
new Thread(ad).start();
}
}
运行程序,程序并不是总能分别输出0-9这10个数字:
这是因为,java中的自增操作并不是原子操作。i++ 的操作实际上分为三个步骤“读-改-写”,例如下面这道面试题:
int i = 10;
int a = i++; //10
实际上是分为下面的步骤(仅模拟):
int temp = i;
i = i + 1;
a = temp;
而在本例中,由于没有将自增后的值赋给其他变量,实际上是分为了下面两个步骤:
int temp = serialNumber; // 第一步
serialNumber = serialNumber + 1; //第二步
所以出现上述情况的原因是,当某一个线程A执行完第一步但还没有执行第二步时,读取到某个值比如是5,这时候另一个线程B执行完了第一步和第一步,即读取到了值5,并且将其修改为6然后输出,然后线程A继续执行第二步,也将值5自增得到的6输出了,所以结果中就出现了两个“6”。
我们尝试将serialNumber用volatile修饰,发现也不能解决问题,这是因为,volatile只能解决内存可见性的问题,并不能解决原子性的问题。要解决这个问题,JUC为我们提供了一些原子变量,放在java.util.concurrent.atomic包下:
例如,我们现在使用AtomicInteger代替int类型:
package concurrent;
import java.util.concurrent.atomic.AtomicInteger;
class AtomicDemo implements Runnable {
private AtomicInteger serialNumber = new AtomicInteger(0);
public int getSerialNumber() {
return serialNumber.getAndIncrement();
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getSerialNumber());
}
}
public class TestAtomic {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for(int i=0;i<10;i++)
new Thread(ad).start();
}
}
运行程序,不会再输出重复的元素:
那么,这些原子变量类型是如何实现同步效果的呢?
首先,原子变量类型中的所有域都使用了volatile关键字修饰以保证内存可见性;然后,使用了CAS(Compare and Swap)算法保证数据变量的原子性。
CAS算法
CAS算法是硬件对于并发操作的支持,其中包含了三个操作数:内存值,预估值和更新值。每当要执行更新操作时,会先在同步方法中比较内存值和预估值是否相等,如果相等才会用更新值替换内存值,否则什么也不做。
下面我们可以用一段程序模拟一下CAS算法:
package concurrent;
/*
* 模拟CAS算法
*/
class CAS {
// 内存值
private volatile int value;
// 获取内存值
public synchronized int getValue() {
return value;
}
//无论更新成功还是失败,都会返回旧的内存值
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
this.value = newValue;
}
return oldValue;
}
//判断更新是否成功,如果更新成功,旧的内存值会和预估值相等
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
public class TestCAS {
public static void main(String[] args) {
CAS cas = new CAS();
//创建10个线程来模拟多线程环境
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.getValue();
boolean b = cas.compareAndSet(expectedValue, (int) (Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
运行程序,发现输出结果既有成功,也有失败,这是符合逻辑的: