本文为看视频学习记录,若有错误,请指正,谢谢!
首先介绍一下i++操作的原子性
i++的操作实际上分成3个步骤“读-改-写”
例如:
Int i = 10;
I = i++;
实际上
Int temp = i;
I = i + 1;
I = temp;
看一段代码,表明在开发过程中原子性问题的表现:
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 serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(5000);
}catch (InterruptedException r){
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}
运行结果:
进运行一次就发生了问题,看上图红框的内容,两个线程同时输出了8,也就是说两个线程对同一个共享变量进行操作,出现了相同的结果,这就说明上面的代码并没有保证原子性。
造成这样的结果的原因是:当线程运行时,会分配独立地缓存,主存中有一个共享数据,线程们若要对数据进行操作,需要线程从主存中读取共享变量的值,复制到独立的缓存中来,然后进行“++”的操作,然后再写回到主存中去。但是此时可能有线程2也进行了与线程1同样的操作,那么就导致了问题的出现。至于volatile能够解决内存可见性的问题,但是针对原子性问题,volatile并不能起作用,不能够保证是否有先后的问题。假设我们能够用volatile来修饰变量,也可以看做两个线程的事情都是在主存中进行操作,但是当线程1对变量操作后写回主存之前,线程2也进行读—改—写的操作,依然存在原子性的问题。最主要的原因是因为i++这个操作是读-改-写的操作,所以volatile只能保证内存可见性的问题,但是不能解决原子性的问题。
于是就有了原子变量这个概念,在jdk1.5之后,java.Util.Concurrent.Atomic包下提供了常用的原子变量
原子变量的特性:
1.是volatile修饰的,保证内存可见性
2.CAS(compare and swap)算法保证数据的原子性:当多个线程对主存中的数据进行修改的时候,有且只有一个线程会成功
CAS算法是硬件对于并发操作共享数据的支持。
Cas包含了三个操作数:
内存值V,预估值A,更新值B。当且仅当 V == A时,V = B时,否则将不做任何操作。
可以将CAS看成是两个操作,第一个读取V的值,第二个,对比A和B的值是否一致,然后复制,上述的每一步都是同步(一次只允许一个线程访问)的。
上述图可以看出,如果线程2在线程1将数据写回主存之前读取了serialNumber的值,然后进行操作,就会导致预估值和内存值不相等,而不做任何的操作。
而且,CAS算法的效率要比加锁要高,当线程当前执行的任务不能够成功时,线程不会像加锁时一样,被阻塞,放弃当前CPU的使用时间,而是不断的再次尝试,知道成功为止。
修改后的代码:
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 AtomicInteger serialNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(5000);
}catch (InterruptedException r){
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();//i++
//return serialNumber.incrementAndGet();//++i
}
}
运行结果:
成功!
模拟CAS算法
public class TestCompareAndSwap {
public static void main(String[] args){
final CompareAndSwap cas = new CompareAndSwap();
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() * 100));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//获取内存值
public synchronized int getValue() {
return value;
}
public void setValue(int value) {
this.value = 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);
}
}
运行结果:
主要操作,在每次操作进行之前,需要从内存中读取共享变量的值。在真正进行比较和替换之前,再一次读取主存中的值,然后进行比较,若两者的值是相等的,那就进行修改操作,否则,什么都不做!