Java并发编程(二):原子变量和CAS算法

原子变量和CAS算法

什么是原子性?

不可分割的操作,要么全部做了,要么一点也不做

i++的原子性问题

/**
 * 实际上i++分为三个步骤 “读-改-写”
 * int i = 0;
 * i++;
 * 实际上的操作:
 * int tmp = i;
 * tmp = i+1;
 * i = tmp;
 */

所以,i++操作并不是原子性的

看下面的一段代码:

public class Main {
    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;

    public int getSerialNumber()
    {
        return serialNumber++;
    }
    @Override
    public void run() {

        try{
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName()+" : "+getSerialNumber());
        }
        catch (Exception e )
        {
        }
    }
}
代码逻辑:
  • 开10个线程,每个线程都对serialNumber做加一操作

实际上运行结果:

Thread-6 : 2
Thread-8 : 0
Thread-1 : 7
Thread-2 : 6
Thread-7 : 1
Thread-3 : 5
Thread-0 : 8
Thread-9 : 0
Thread-5 : 3
Thread-4 : 4

注意:由于线程并发冲突并不是每次都发生的,发生后产生的结果也不尽相同

  • 可以看到结果出现了问题(出现了两个0,没到到达9)

发生了什么?

  • 多个线程同时访问主存中的共享数据
    • 线程1拿到 serialNumber加一打印,同时线程2 也拿到serialNumber并加一打印
    • 造成了加一结果重复
    • 即使用volatile关键字,也没办法保证原子性问题

怎么解决?

一、使用原子变量

可以参见java.util.concurrent.atomic包中提供的一些保证原子性的类(jdk1.5之后提供)

  • volatile保证内存可见性
  • CAS算法保证数据的原子性,CAS是硬件对并发操作共享数据的支持

什么是CAS?

Java中CAS的过程:

  • 内存位置(Java中简单理解为变量内存地址,用V表示)

  • 旧的预期值A,准备设置的新值B

  • 当且仅当V符合A时,处理器才会用B更新V,否则不执行更新,最终无论是否更新都返回V的地址

这种算法就保证了修改操作的原子性

为什么比锁效率高?

即使CAS操作不成功,也不会阻塞,而是再次尝试,直到成功

注意:如果尝试的次数过多,CAS操作的效率可能还不如锁,这时候一些轻量级锁就会进行粗化操作,详细的说明可以去看JVM原理相关资料

将代码中的int类变为AtomicInteger类,如下:

import java.util.concurrent.atomic.*;
public class Main {
    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();

    public int getSerialNumber()
    {
        //获取并递增
        return serialNumber.getAndIncrement();
    }
    @Override
    public void run() {
        try{
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName()+" : "+getSerialNumber());
        }
        catch (Exception e )
        {
        }
    }
}

执行结果:

Thread-4 : 2
Thread-8 : 7
Thread-7 : 8
Thread-3 : 3
Thread-9 : 6
Thread-5 : 4
Thread-6 : 5
Thread-2 : 1
Thread-1 : 0
Thread-0 : 9

从0-9,结果正确

原创文章 40 获赞 16 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/105117070