乐观锁CAS的简单理解

废话不不多说,先上代码:

public class Test {
	private static int count = 0;
	public static void main(String[] args) {
		for (int i = 0; i < 2; i++) {
			new Thread() {
				public void run() {
					for (int i = 0; i < 200; i++) {
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						count++;
					}
				};
			}.start();
		}
		try {
			Thread.sleep(3000);
			System.out.println("===" + count);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

上述代码最终System.out是的多少?400?答案是不确定的,有可能是400,有可能比400小的数。这是因为是线程不安全造成的(可见性)。线程A修改了成某个值之后,相当于只更新了其工作内存的值,没有马上更新到主内存上。所以线程B从主内存读取的时候还是旧的值。

加上线程安全的悲观锁synchronized,能解决并发问题,代码如下:

public class Test {
	private static int count = 0;
	public static void main(String[] args) {
		for (int i = 0; i < 2; i++) {
			new Thread() {
				public void run() {
					for (int i = 0; i < 200; i++) {
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						synchronized (Test.class) {
							count++;
						}
					}
				};
			}.start();
		}
		try {
			Thread.sleep(3000);
			System.out.println("===" + count);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

这一次,加上同步锁后,最终输出结果必然是400。Synchronized虽然确保了线程的安全,但是在性能上却不是最优的,Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。

原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类,使用如下:

public static void main(String[] args) {
        // 初始化内存值是10
		final AtomicInteger value = new AtomicInteger(10);
		new Thread(){
			public void run() {
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "..." + value.get());
				value.compareAndSet(10, 20);
				System.out.println(Thread.currentThread().getName() + "..." + value.get());
			};
		}.start();
		new Thread(){
			public void run() {
				System.out.println(Thread.currentThread().getName() + "###" + value.get());
				value.compareAndSet(10, 30);
				System.out.println(Thread.currentThread().getName() + "###" + value.get());
			};
		}.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
// 结果是:
Thread-1###10
Thread-1###30
Thread-0...30
Thread-0...30

java.util.concurrent.atomic完全建立在CAS之上,CAS有三个操作数,内存值V、旧的预期值A、要修改的值B,如果 V == A, 那么 V = B,返回true;否则什么都不做返回false。
1.CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术
2.CAS是原子操作,保证并发安全,而不能保证并发同步
3.CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令)
4.CAS是非阻塞的、轻量级的乐观锁
5.在Java中 CAS 底层使用的就是自旋锁(是指尝试去获取锁的线程不会立即阻塞,而是采用循环的方式去获取锁,这样的好处是减少线程上下文切换消耗,缺点是循环会消耗CPU) + UnSafe类。

CAS优点:

非阻塞的轻量级的乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁、解锁和唤醒操作。

CAS缺点:

1、CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2、不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。因为它本身就只是一个锁住总线的原子交换操作啊。两个CAS操作之间并不能保证没有重入现象。
3、ABA问题
线程C、D;线程D将A修改为B后又修改为A,此时C线程以为A没有改变过,java的原子类AtomicStampedReference,通过控制变量值的版本号来保证CAS的正确性。具体解决思路就是在变量前追加上版本号,每次变量更新的时候把版本号加一,那么A - B - A就会变成1A - 2B - 3A。

public static void main(String[] args) {
        AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1 );
            stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1 );
        },"A").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(b); // false
            System.out.println(stampedReference.getReference()); // 100
            System.out.println(stampedReference.getStamp()); // 3
        },"B").start();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值