Java并发_cas、aqs

本文介绍了Java并发中的CAS(Compare and Swap)机制及其自旋锁的问题,包括CPU占用和不可重入性。同时深入探讨了AQS(AbstractQueuedSynchronizer)框架,它是实现多线程同步的基础,如ReentrantLock、Semaphore和CountDownLatch等。AQS通过state变量和FIFO队列管理同步状态,并支持独占和共享两种模式。ReentrantLock作为独占锁的示例,其公平性与非公平性与AQS策略有关。
摘要由CSDN通过智能技术生成

总结来说,cas(compare and swap)按照字面意思就可以理解,每次都从某块内存区域拿到旧值和期望值进行比较,如果相等就修改为新值。如果不相等则会自旋( While(! compareAndSwap(old,current) ),这里放一下我学习自旋锁的网址

https://blog.csdn.net/qq_34337272/article/details/81252853

当然自旋锁会有两个问题:

  1. 因为自旋锁是一种乐观锁,当期望值和内存值不相等时会一直保持自选状态,那么显然当线程竞争激烈时长时间自旋不成功则会占用cpu较高(可以通过设置自旋次数来解决)
  2. 无法重入,想象一下当持有这把锁的线程想再次获取该锁的时候,cas依旧会失败。(可以通过引入计数器来解决)
public void lock() {
			Thread cur = Thread.currentThread();
			if(cur == cas.get()){
				//如果是该线程,那么计数器++
				count++;
				return;
			}
			//不是该线程,cas获取锁
			while (!cas.compareAndSet(null, current)) {
            // DO nothing
        }		
    }
    public void unlock() {
        Thread cur = Thread.currentThread();
        if (cur == cas.get()) {
            if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
                count--;
            } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
                cas.compareAndSet(cur, null);
            }
        }
    }

以下总结来自于

https://blog.csdn.net/vanchine/article/details/111772190?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242

AQS (AbstractQueuedSynchronizer)提供了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。 他维护了一个volatile int state ,线程通过修改(加/减指定的数量)state是否成功来决定当前线程是否成功获取到同步状态(也就是对state进行cas操作) ,同时还维护了一个FIFO队列(先进先出队列),当cas失败时,公平锁会选择将线程放入队列;而非公平锁则会不断自旋。

独占锁和共享锁
AQS支持两种获取同步状态的模式既独占式和共享式。独占式模式同一时刻只允许一个线程获取同步状态,而共享模式则允许多个线程同时获取。ReentrantLock就是以独占方式实现的互斥锁,ReadWriteLock的读锁是共享锁(显然读取数据并不会修改数据,所以没有必要独占),写锁是独占锁,Semaphore是一种共享锁(state设置为可进入的线程数)。

以ReentrantLock举例:

//指定锁类型
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//默认为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//实现公平锁
private ReentrantLock reentrantLock = new ReentrantLock(true);

Synchronized而言,是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

下面对一部分ReentranLock源码进行分析:

ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();

--FairSync
//公平锁中竞争锁
final void lock() {
	//都按照FIFO来竞争锁
    acquire(1);
}

--UnFairSync
//非公平锁中竞争锁
final void lock() {
·	//任何线程都可以先通过CAS去抢占锁
    if (compareAndSetState(0, 1))
    	//设置独占锁
    	setExclusiveOwnerThread(Thread.currentThread());
    else
    	//抢占失败,按照FIFO来竞争锁
    	acquire(1);
}

--AbstractQueuedSynchronizer
//释放锁就一种实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {
    	// Node就是一个包含了线程信息的节点,放在了队列中
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	//解除挂起状态
        	unparkSuccessor(h);
        return true;
    }
    return false;
}

其中的acquire方法来源于AQS实现:

public final void acquire(int arg) {
    //tryAcquire可以理解为:尝试直接获取独占锁
    //tryAcquire失败之后将当前线程封装Node添加到AQS队列尾部(addWaiter)
    ///并继续循环获取锁(acquireQueued)
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        )
        selfInterrupt();
}

--FairSync
protected final boolean tryAcquire(int acquires) {
	...
	final Thread current = Thread.currentThread();
	int c = getState();
    if (c == 0) {
    	//hasQueuedPredecessors()首先判断是否有头尾节点
    	//然后判断是否有后继节点,最后判断后继节点是否为当前线程
    	// hasQueuedPredecessors()判断成功返回的是False
   		//return h != t && 是否有头尾节点
   		//( (s = h.next) == null || 是否有后继节点
   		// s.thread != Thread.currentThread() ); 是否为当前线程
    	//有则需要继续进行CAS
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }	...
}
--UnFairSync
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //获取status值,如果为0则表示仍然可以再CAS
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
        	//设置当前持有锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //status不为0,继续判断获取锁的线程是否为当前线程
    else if (current == getExclusiveOwnerThread()) {
    	//如果是则status加1,增加重入次数
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //如果都不是则返回false
    return false;
}

//TryAcquire失败则需要添加到队列尾
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //先去获取当前节点的prev(前驱)节点
            final Node p = node.predecessor();
            //前驱节点是head则再次尝试获取锁
            if (p == head && tryAcquire(arg)) {
                //获取锁之后设置head为当前node并清空node的线程信息和prev
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //获取锁失败判断是否需要挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                //前驱节点状态为SIGNAL,当前线程park阻塞
                //LockSupport.park/unpark为线程提供一个类似开关的功能
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            //取消获取锁,主要就是设置节点waitStatus为cancelled,清空节点内容
            //最主要的是unpark为下一个线程放行
            cancelAcquire(node);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值