设为首页 加入收藏

TOP

JAVA的CAS机制
2019-05-12 02:29:08 】 浏览:58
Tags:JAVA CAS 机制
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/charry_a/article/details/79128322

一、什么是CAS


CAS即比较并替换,是一种轻量级锁,一般用于并发量不大的场景,CAS机制中用了3个变量:内存值V,旧的预期值A,要修改的新值B;只有当内存中的值和旧的预期值相等的情况下才更新值为B,否则该线程会一直自旋等待,下面我们用大白话来解释CAS。

二、CAS 应用


想象一下假如现在我们有2个线程,对共享变量进行i++操作,如果不加锁会出现什么情景呢,先看一个代码

	private static int count = 0;
	
	public static void main(String[] args) {
		CountDownLatch latch = new CountDownLatch(2);
		for(int i=0;i<2;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					for(int i=0;i<100;i++) {
						count++;
					}
					latch.countDown();
				}
			}).start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("count=" + count);
	}
这份代码理想情况下,按照预期会输出count=200,但是大多数情况下它的输出都小于200,这是因为i++操作不是原子的,这个时候我们可能会想到加锁,先来看看加synchronized
public class JAVA的CAS机制 {
	
	private static int count = 0;

	public static void main(String[] args) {
		CountDownLatch latch = new CountDownLatch(2);
		for(int i=0;i<2;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (JAVA的CAS机制.class) {
						for(int i=0;i<100;i++) {
							count++;
						}
					}
					latch.countDown();
				}
			}).start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("count=" + count);
	}
}
这份代码会输出count=200,因为我们通过synchronized来保证每次只有一个线程对共享变量进行操作,虽然synchronized可以解决第一份代码的问题,但是synchronized相当于一个悲观锁,每次只允许一个线程操作。那我们这里要说的CAS是怎么用呢,它是怎么解决第一份代码的问题呢,我们先看一段代码

private static AtomicInteger count = new AtomicInteger(0);

	public static void main(String[] args) {
		CountDownLatch latch = new CountDownLatch(2);
		for (int i = 0; i < 2; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					for (int i = 0; i < 100; i++) {
						count.getAndIncrement();
					}
					latch.countDown();
				}
			}).start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("count=" + count.get());
	}

这份代码输出一直都是count=200,这里使用了JAVA原子包里面提供的类atomicInteger,这是一个原子操作类,如果大家感兴趣可以看看它的源代码,底层就是使用了CAS机制来实现,CAS相当于synchronized的轻量级实现,是一个乐观锁,假如某个线程没有更新到值,那么它会一直自旋并不断重试,直到最后更新值。在并发量不大的时候,建议大家使用CAS。

三、AtomicInteger源码分析

JAVA的CAS机制大量用在了JAVA并发原子类里面,这里我们以AtomicInteger类来分析其具体实现

先来看定义


private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    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;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }

    /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(int newValue) {
        value = newValue;
    }
其中Unsafe类是CAS的核心实现类,JAVA通过它来访问本地方法;而valueOffset则是变量在内存中的偏移地址,
;而用valatile修饰得变量value保证了多线程内存可见性。

再看看getAndIncrement

  /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
调用unsafe.getAndInt,假设当前内存中变量值为2,同时有2个线程来操作这个变量,假设A线程拿到了这个变量,准备修改为3,B线程也准备修改这个变量,发现目前的值3和自己手里的变量副本2不一样,所以更新失败,这就相当于使用了CAS

四、CAS的问题

4.1 CPU消耗大

CAS线程会不停自旋,如果并发量大的话,将会不停重试,还不释放CPU,极端情况下会耗光资源。

4.2 只能保证某个变量的原子操作

从第三份代码我们看到,CAS只能保证单个变量的原子性,不能保证某个代码块或者某个对象的原子性(这里可以用并发包里面的原子更新类AtomicReference实现)

4.3 经典的ABA问题

就是说变量A在某个线程修改后成B后,另一个线程又将其修改为A,导致其他线程误认为这个变量实际没有变过,这就是经典的ABA问题,要解决这个,我们可以加一个版本号,每次比较的时候,不仅需要比较值,还需要看版本号是否一致,一致才修改,在JAVA并发包里面AtomicStampedReference类就是用了版本号来解决ABA问题。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇redis 报错 Redis protected-mode.. 下一篇Git常用命令

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目