并发深度解析CAS&AQS

CAS

(1) 概念:CompareAndSet 一种无锁的原子算法,使用乐观锁, 如:版本控制,CAS可以保证一次的读-改-写操作是原子操作,在单处理器上该操作容易实现,但是在多处理器上实现就有点儿复杂了。
悲观锁:写(增删改)的操作多,读(查)的少 Lock
乐观锁:读的多,写的少
(2) 思想:给你一个期望值,与你现在的值比较,如果相等立即修改,反之不能修改什么也不做CAS(V,E,N)
(3)作用及优点:CAS实现稍微复杂,无锁,不存在阻塞,提高了效率,CPU的吞吐量,性能好
(4)缺点:CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方法:循环时间太长、只能保证一个共享变量原子操作、ABA问题。

  • 循环时间太长:如果CAS一直不成功呢?这种情况绝对有可能发生,如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。在JUC中有些地方就限制了CAS自旋的次数,例如BlockingQueue的SynchronousQueue。
  • 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量整成一个变量,利用CAS也不错。例如读写锁中state的高地位
  • ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。改进:使用 AtomicStampedReference
    (5) 场景:
  • 应用于简单的数据计算
  • 适用线程冲突比较小的场景
public class CAS1{
	
	private static volatile int m = 0;

	private static AtomicInteger atomicI = new AtomicInteger (0);
	
	public static void increase1(){
		m++;//反编译javap -c 这里使用了3条指令,iconst_1、iadd、putstatic,CPU会时间片轮询所以会出现不一致问题
	}

	public static void increase2(){
		atomicI .incrementAndGet();//反编译javap -c 这里使用了1条 invokevirtual 指令
	}


	public static void main(String[] args) throws Exception{
		Thread[] t = new Thread[20];
		for(int i = 0; i < 20; i++){
			t[i] = new Thread(()->{
				CAS1.increase1();
			});
			t[i].start();
			t[i].join();//join方法加入 group main()//join线程有了交互性。就是让t[i]先执行
		}
		System.out.println(m);//小于20

		Thread[] tf = new Thread[20];

		for(int i = 0; i < 20; i++){
			tf[i] = new Thread(()->{
				CAS1.increase2();
			});
			tf[i].start();
			tf[i].join();
		}
		System.out.println("atomic:"+atomicI.get());
	}
}
AtomicInteger 原码分析

unsafe——后门类,调用CPU指令
valueOffset——地址偏移量,有一个初始地址+地址偏移量=真实地址
value——要修改的值,要保证可见性所以使用volatile

public class AtomicInteger extends Number implements java.io.Serializable {
    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;

	 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }


Unsafe原码分析

var1——目标值
var2——期望值
var4——更改值

public final class Unsafe {

	@CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {//通过JVM调用底层指令
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
    
	public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
}
JVM底层实现的代码

在这里插入图片描述
就是用Lock和 cmpxchg指令来实现的
在这里插入图片描述
incrementAndGet()->unsafe->unsafe.cpp->汇编cmpxchg,用硬件去保证原子性

AQS

AbstractQueueSynchronizer 同步发生器 构建LOCK,封装性比较高,属于JUC包下,作用于子类定义非公共内部帮助类,写锁的时候的一个帮助器,提供获取锁,如:ReentrantLock重入锁也会用到AQS、ReadWriteLock、Condition
AQS定义两种资源共享方式:Exclusive(acquire独占方法,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

  • 基本思想:通过内置的 FIFO 同步队列来完成线程争夺资源的管理工作
  • 方法:
    • acquire(int arg) 以独占模式获取对象,忽略中断
    • acquireShared(int arg) 以共享模式获取对象,忽略中断
    • tryAcquire(arg) 试图在独占模式下获取对象状态
    • tryAcquireShared(int arg) 试图在共享模式下获取对象状态
    • release(int arg) 以独占模式释放对象
    • releaseShared(int arg) 以共享模式释放对象
  • CLH 同步队列:把每个线程看做一个节点Node,一个指针指向pre前驱和有一个指针指向next后继,有一个头结点就也就是同步器的(傀儡节点),整个工作过程如下:
    • 创建同步器(傀儡节点)
    • 当有新的线程要进入的时候先去获取 volatile state的状态是否等于0 ,如果等于直接执行,反之当 state >=1的时候就要把自己链接到傀儡节点上,傀儡节点的node.head=node1.pre ,傀儡节点的node.tail=node1.next
    • 当第二个线程进入的时候发现state 还是 >=1 则将自己链接到前一个线程Node上,将node1.next = node2, node.tail = node2.next
    • CLH同步队列的线程节点会进行自旋 去获取锁(tryAcquire),默认是公平竞争锁
      在这里插入图片描述

AQS内部线程节点

CANCELLED——因中断、完成、退出队列
SIGNAL——节点的继任者被阻塞
CONDITION——条件阻塞
PROPAGATE——共享模式 头结点的状态

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
ReentrantLock

公平锁
非公平锁

ReentrantReadWriteLock

ReentrantReadWriteLock 实现了 ReadWriteLockReadWriteLock有两个方法 readLock() 返回读取锁、writeLock()返回写入锁
这里的读采用的是非锁的机制, 读取者优先或写入者优先强加给锁访问的排序。但是,它确实支持可选的公平策略。主要是读写分离

锁降级

首先获取readLock(),读取之后释放读锁readLock.unLock(),然后去writeLock()获取写锁,wreteLock.lock()给写锁加锁,再要释放写锁之前我们要加上读锁readLock.lock() 最后进行释放写锁writeLock.unLock()这就是锁降级

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值