JVM学习-CAS与原子类

1.CAS

CAS 即 Compare and Swap ,它体现的一种乐观锁的思想,比如多个线程要对一个共享的整型变量执 行 +1 操作:

// 需要不断尝试 
while(true) {
    
       
	int 旧值 = 共享变量 ; // 比如拿到了当前值 0  
	int 结果 = 旧值 + 1; // 在旧值 0 的基础上增加 1 ,正确结果是 1  
	   
	/*  这时候如果别的线程把共享变量改成了 5,本线程的正确结果 1 就作废了,这时
		compareAndSwap 返回 false,重新尝试,直到:    
		compareAndSwap 返回 true,表示我本线程做修改的同时,别的线程没有干扰  
	*/  
	//这里其实是把共享变量的值与旧值做比较,如果相同,就可以安全的把结果写入共享变量
	if( compareAndSwap ( 旧值, 结果 )) {
    
         
	// 成功,退出循环  
	} 
}

CAS对共享变量可以不用加锁。
**获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。**结合 CAS 和 volatile 可以实现无 锁并发,适用于竞争不激烈、多核 CPU 的场景下

  • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
  • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
    线程陷入阻塞,会涉及到线程的上下文切换,这样就严重降低了效率。

CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令,下面是直接使用 Unsafe 对象进 行线程安全保护的一个例子

import sun.misc.Unsafe; 
import java.lang.reflect.Field; 
public class TestCAS {
    
        
	public static void main(String[] args) throws InterruptedException {
    
    
		DataContainer dc = new DataContainer();        
		int count = 5;
		Thread t1 = new Thread(() -> {
    
    
			for (int i = 0; i < count; i++) {
    
                    
				dc.increase();
			}
		});
		t1.start();
		t1.join();
		System.out.println(dc.getData());
	}
}
class DataContainer{
    
    
	private volatile int data;
	static final Unsafe unsafe;
	static final long DATA_OFFSET;
	static {
    
    
		try {
    
    
			// Unsafe 对象不能直接调用,只能通过反射获得
			Field theUnsafe =Unsafe.class.getDeclaredField("theUnsafe");
			theUnsafe.setAccessible(true);
			unsafe = (Unsafe) theUnsafe.get(null);
		}catch(NoSuchFieldException | IllegalAccessException e) {
    
    
			throw new Error(e);
		}
		try {
    
    
		// data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性。
		//方便定位data的地址。
		DATA_OFFSET=
		unsafe.objectFieldOffset(DataContainer.class.getDeclaredField("data"));
		} catch (NoSuchFieldException e) {
    
    
			throw new Error(e);
		}
	}
	public void increase(){
    
    
		int oldValue;
		while(true) {
    
    
			// 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解
			oldValue = data; 	
			// cas 尝试修改 data 为 旧值 + 1,如果期间旧值被别的线程改了,返回 false
			/*第一个参数当前DataContainer对象,第二个参数偏移量。
			对前面两个信息我们知道,针对哪个字段做compareandSwap。
			后面的参数就是旧值和新值,它首先会把oldValue与共享变量的当前值做比较。
			如果两个不一致,表明修改失败,然后我就重新进入while循环,直到旧值和共享变量的值一致了。
			然后compareandswap才会返回true,并且新值更新到共享变量data里去。
			if条件成立,while就结束了    
			*/        
			if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue + 1)) {
    
    
				return;            
			}
		}    
	}

	public void decrease() {
    
    
		int oldValue;
		while(true) {
    
    
			oldValue = data;
			if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, 
			oldValue - 1)){
    
    							    
				return; 
			}
		}
	}

2 乐观锁与悲观锁

CAS 是基于乐观锁的思想:乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系, 我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:悲观的估计,得防着其它线程来修改共享变量,我上了锁 你们都别想改,我改完了解开锁,你们才有机会

3 原子操作类

juc(java.util.concurrent)中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、 AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的。 可以使用 AtomicInteger 改写之前的例子:

// 创建原子整数对象 
private static AtomicInteger i = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
    
    
	Thread t1 = new Thread(() -> {
    
    
		for (int j = 0; j < 5000; j++) {
    
    
			i.getAndIncrement();  // 获取并且自增  i++ 
			//                i.incrementAndGet();  // 自增并且获取  ++i        
		}    
	});
	Thread t2 = new Thread(() -> {
    
    
		for (int j = 0; j < 5000; j++) {
    
    
			i.getAndDecrement();  // 获取并且自增  i++ 			
		} 
	});
	 t1.start();
	 t2.start();
	 t1.join();
	 t2.join();
	 System.out.println(i);
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/113662402