AQS CAS分析

什么是CAS
CAS(Compare And Swap)比较并交换。是解决多线程并行情况下使用锁造成的性能损耗的一种机制,CAS操作包含三个操作数— 内存位置(V)预期原值(A)和新值(B).如果内存位置与预期原值相匹配,那么处理器会自动将新值更新到该位置值,否则处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含A;如果包含该值,则将B值放到这个位置”;否则不要更改该位置,只告诉我这个位置现在的值即可。

在JAVA中,sum.misc.Unsafe类提供了native 硬件级别的原子操作来实现这个CAS,java.util.concurrent 包下的大量类都使用了这个Unsafe.java 类,至于Unsafe 的具体实现这里不做讨论。

CAS 典型应用

java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的,下面以AtomicInteger 的部分实现来大致讲解下这些原子类的实现。讲解之前提到一个知识点就是 valatile 关键字,关于valatile之前帖子有过介绍,可以进行翻阅。

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 volatile int value;// 初始int大小
    // 省略了部分代码...
 
    // 带参数构造函数,可设置初始int大小
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    // 不带参数构造函数,初始int大小为0
    public AtomicInteger() {
    }
 
    // 获取当前值
    public final int get() {
        return value;
    }
 
    // 设置值为 newValue
    public final void set(int newValue) {
        value = newValue;
    }
 
    //返回旧值,并设置新值为 newValue
    public final int getAndSet(int newValue) {
        /**
        * 这里使用for循环不断通过CAS操作来设置新值
        * CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系
        * */
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
 
    // 原子的设置新值为update, expect为期望的当前的值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
 
    // 获取当前值current,并设置新值为current+1
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
 
    // 此处省略部分代码,余下的代码大致实现原理都是类似的
}

一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多(查看getAndSet(),可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。
在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:


public class Counter {
    private int count;
    public Counter(){}
    public int getCount(){
        return count;
    }
    public void increase(){
        count++;
    }
}

上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:


public class Counter {
    private int count;
    public Counter(){}
    public synchronized int getCount(){
        return count;
    }
    public synchronized void increase(){
        count++;
    }
}

这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了Atomic下的原子类的实现是类似乐观锁的,效率会比使用 synchronized 关系字高,推荐使用这种方式,实现如下:

public class Counter {
    private AtomicInteger count = new AtomicInteger();
    public Counter(){}
    public int getCount(){
        return count.get();
    }
    public void increase(){
        count.getAndIncrement();
    }
}
// 或者读写锁的方式
public class Counter {
    private volatile int count = 0;
    private static final ReadWriteLock lock = new ReentrantReadWriteLock();
    public Counter(){}
    public int getCount(){
    	Lock readLock = lock.readLock();
    	readLock.lock();
    	try{
    		return count;
    	} finally {
    		readLock.unlock();
    	}
    }
    public void increase(){
        Lock writeLock = lock.writeLock();
    	writeLock.lock();
    	try{
    		count+=1;
    	} finally {
    		writeLock.unlock();
    	}
    }
}
AQS (AbstractQueuedSynchronizer)

什么是AQS
AQS(AbstractQueuedSynchronizer), AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计作为一些可用原子int值来表示状态的同步器基类。如果有看过类似CountDownLatch 或者ReentrantLock的一些源码实现,会发现其内部有一个继承了AbstractQueuedSynchronizer的内部类 Sync。可见这些类是基于AQS框架来实现的一个同步器,类似的同步器在JUC下还有不少。

AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)

使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getState,setState,compareAndSetState这几个方法来设置获取状态

  • boolean tryAcquire(int arg)
  • boolean tryRelease(int arg)
  • int tryAcquireShared(int arg)
  • boolean tryReleaseShared(int arg)
  • boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquire、 tryRelease、isHeldExclusively而支持共享获取的同步器应该实现tryAcquireShared、tryReleaseShared、isHeldExclusively。
下面以 CountDownLatch 举例说明基于AQS实现同步器, CountDownLatch 用同步状态持有当前计数,countDown方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await调用acquire,如果计数器为0,acquire 会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:

public class CountDownLatch {
    /**
     * 基于AQS的内部Sync
     * 使用AQS的state来表示计数count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
 
        Sync(int count) {
            // 使用AQS的getState()方法设置状态
            setState(count);
        }
 
        int getCount() {
            // 使用AQS的getState()方法获取状态
            return getState();
        }
 
        // 覆盖在共享模式下尝试获取锁
        protected int tryAcquireShared(int acquires) {
            // 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
            return (getState() == 0) ? 1 : -1;
        }
 
        // 覆盖在共享模式下尝试释放锁
        protected boolean tryReleaseShared(int releases) {
            // 在for循环中Decrement count直至成功;
            // 当状态值即count为0的时候,返回false表示 signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
 
    private final Sync sync;
 
    // 使用给定计数值构造CountDownLatch
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
 
    // 让当前线程阻塞直到计数count变为0,或者线程被中断
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
 
    // 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回true
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
 
    // count递减
    public void countDown() {
        sync.releaseShared(1);
    }
 
    // 获取当前count值
    public long getCount() {
        return sync.getCount();
    }
 
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

一下是基于AQS写的一个独占锁简单的例子

package com.stylefeng.guns.AQS;

import java.util.Collection;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/** 独占锁
* @author climb.s
* @date 2018/10/12 14:39 */
public class Mutex {
   // 仅需要将操作代理到Sync上
   private final Sync sync = new Sync();

   // 静态内部类,自定义同步器
   private static class Sync extends AbstractQueuedSynchronizer {
   	// 是否处于占用状态
   	@Override
   	protected boolean isHeldExclusively() {
   		return getState() == 1;
   	}

   	// 当状态为0时获得锁
   	@Override
   	protected boolean tryAcquire(int state) {
   		// 调用CAS 方法,判断是否处于占用状态,如果空闲,则占用(0:非占用状态 1:占用状态)
   		if (compareAndSetState(0, 1)) {
   			// 设置独占的线程
   			setExclusiveOwnerThread(Thread.currentThread());
   			return true;
   		}
   		return false;
   	}

   	// 释放锁,将状态设置为0
   	@Override
   	protected boolean tryRelease(int state) {
   		if (getState() == 0) throw new IllegalMonitorStateException("未占用,无需释放锁");
   		setExclusiveOwnerThread(null);
   		setState(0);
   		return true;
   	}
   }

   // 获取等待的线程
   public Collection<Thread> getQueuedThreads() {
   	return sync.getQueuedThreads();
   }

   // 独占所操作接口
   public void lock() {
   	sync.acquire(1);
   }

   // 释放独占锁
   public void unlock() {
   	sync.release(0);
   }
}
package com.stylefeng.guns.AQS;

import java.util.Random;

/** @author climb.s
* @date 2018/10/12 14:56 */
public class MutexExample {
   private static Random random = new Random(47);
   private static Mutex  mutex	 = new Mutex();

   private static class Weight implements Runnable {
   	String name;

   	public Weight(String name) {
           this.name = name;
       }

   	@Override
   	public void run() {
   		mutex.lock();
   		System.out.println(name + " 放苹果!");
   		System.out.println(name + " 重量:" + (random.nextInt(10) + 3));
   		System.out.println(name + " 取苹果!");
           printQueuedThreads();
           mutex.unlock();
   	}

   	private static void printQueuedThreads() {
   		System.out.print("等待队列中的线程:");
   		for (Thread thread : mutex.getQueuedThreads()) {
   			System.out.print(thread.getName() + " ");
   		}
   		System.out.println();
   	}
   }

   public static void main(String[] args) {
       Thread[] threads = new Thread[10];
       for (int i = 0; i < 10; i++) {
           threads[i] = new Thread(new Weight("Weight-" + i), "Thread-" + i);
       }
       for (int i = 0; i < 10; i++) {
           threads[i].start();
       }
   }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值