Java并发(六)——CAS、AQS、Lock、通信工具类

CAS、AQS、Lock、通信工具类

1 CAS

Compare and Swap,非阻塞原子性操作

将变量当前值与原值进行比较,当变量当前值等于原值时,替换该变量为新值

CAS多于自旋组合,但自旋会耗费CPU资源。可让JVM支持处理器提供的pause指令,自旋失败时CPU睡眠一小段时间再继续自旋

无法解决ABA问题,即变量原值为A,再该线程准备修改时,另一个线程将变量改为B再改为A。可增加一个版本号标记改变次数

1.1 Unsafe类

Java通过Unsafe类下的方法实现CAS,Unsafe里面是native方法,实现是C++写的

1.2 Atomic包

Atomic包下的方法使用Unsafe实现原子操作

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新字段

在这里插入图片描述

// AtomicInteger 类常用方法

public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

2 AQS

AbstractQueuedSynchronizer抽象队列同步器,是一个用于构建锁和同步器的框架

AQS的核心思想是,如果被请求的资源空闲,则将当前线程设置为有效的工作线程,把资源锁定;如果被请求的资源占用,则将线程加入CLH队列中等待

CLH队列是一个虚拟双向队列,不存在队列实例,仅存在结点之间的关联,AQS将每个线程封装成一个队列的Node

在这里插入图片描述

资源共享模式:

  • 独占模式:一次只能一个线程获取
  • 共享模式:同时可以多个线程获取

AQS内部使用一个volatile的变量state作为资源的标识

AQS底层使用模板方法模式,自定义同步器需要重写以下方法

isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

acquire()部分源码

// arg表示获取资源的个数,独占模式下都为1
public final void acquire(int arg) {
    // 1. 尝试获取资源
    // 2. 获取资源失败则加入队列中
    // 3. 判断当前线程是否是第二个等待的线程,不是则阻塞
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 加入等待队列
private Node addWaiter(Node mode) {
    // 生成该线程对应的Node节点
    Node node = new Node(Thread.currentThread(), mode);
    // 将Node插入队列中
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // 使用CAS尝试,如果成功就返回
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果等待队列为空或者上述CAS失败,再自旋CAS插入
    enq(node);
    return node;
}

// 自旋CAS插入等待队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

// 判断当前线程是否是第二个等待的线程
// 是则自旋尝试获取资源
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋
        for (;;) {
            final Node p = node.predecessor();
            // 如果node的前驱结点p是head,表示node是第二个结点,就可以尝试去获取资源了
            if (p == head && tryAcquire(arg)) {
                // 拿到资源后,将head指向该结点。
                // 所以head所指的结点,就是当前获取到资源的那个结点或null。
                setHead(node); 
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果自己可以休息了,就进入waiting状态,直到被unpark()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

release()部分源码

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

private void unparkSuccessor(Node node) {
    // 如果状态是负数,尝试把它设置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 得到头结点的后继结点head.next
    Node s = node.next;
    // 如果这个后继结点为空或者状态大于0
    // 通过前面的定义我们知道,大于0只有一种可能,就是这个结点已被取消
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 等待队列中所有还有用的结点,都向前移动
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 如果后继结点不为空,
    if (s != null)
        LockSupport.unpark(s.thread);
}

在这里插入图片描述

3 Condition

// Lock接口中有一个方法获取Condition
Condition newCondition();

Condition和Object的wait/nofity方法基本相似。Condition的await方法对应Object的wait方法,Condition的signal/signalAll方法对应Object的nofity/nofityAll方法

Condition可以实现唤醒指定的线程

public class Main {

  static Lock lock = new ReentrantLock();
  static Condition condition1 = lock.newCondition();
  static Condition condition2 = lock.newCondition();

  public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
      try {
        lock.lock();
        System.out.println("thread1 lock");
        condition1.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } finally {
        lock.unlock();
        System.out.println("thread1 unlock");
      }
    });

    Thread thread2 = new Thread(() -> {
      try {
        lock.lock();
        System.out.println("thread2 lock");
//        condition1.signalAll();
        condition2.signalAll();
      } finally {
        lock.unlock();
        System.out.println("thread2 unlock");
      }
    });

    thread1.start();
    try{
      Thread.sleep(1000);
    }catch (InterruptedException e){
      e.printStackTrace();
    }
    thread2.start();
  }

}

4 ReentrantLock

ReentrantLock支持公平锁和非公平锁

4.1 公平锁部分源码

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

4.2 非公平锁部分源码

static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 这里没有对阻塞队列进行判断
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

4.3 总结

非公平锁在调用lock后,会先进行一次CAS抢锁;如果失败了,则进入tryAcquire方法,发现锁被释放了则,则再进行一次CAS抢锁;如果失败了,则跟公平锁一样,进入阻塞队列等待唤醒

5 ReentrantReadWriteLock

支持读锁和写锁,允许多个线程同时读

6 StampedLock

StampedLock是JDK 8发布的,没有实现Lock接口和ReadWriteLock接口,但是实现了读写锁的功能,并且把读锁分为乐观读锁悲观读锁,性能比ReentrantReadWriteLock好

思想:无锁编程思想,与CAS自旋思想一样,在读的时候如果发生了写,则应该通过重试的方式获取新值,不应该阻塞写操作

具体地:在获取乐观读锁后,读操作后,调用validate方法检查期间是否有过写操作,有的话则获取悲观读锁,重新读

7 通信工具类

作用
Semaphore信号量,限制线程的数量
Exchanger两个线程交换数据
CountDownLatch线程等待直到计数器减为0开始工作
CyclicBarrier多个线程到达某处阻塞,全部到达后再继续工作
Phaser增强的CyclicBarrier

7.1 Semaphore

信号量,支持公平锁和非公平锁,可设置信号量为1个或者多个

每次申请信号量,值减1,值为0时申请不到,线程阻塞

实现原理:继承AQS,获取资源失败则进入等待队列

semaphore.acquire();
semaphore.release();

7.2 Exchanger

用于两个线程交换数据

线程调用exchange后会阻塞,直到另一个线程调用了exchange,才会继续执行

Exchanger<String> exchanger = new Exchanger<>();

new Thread(() -> {
    try {
        exchanger.exchange("这是来自线程A的数据"));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

7.3 CountDownLatch

等待多个线程完成某一件事,阻塞的线程才能继续执行

具体地:初始化设置一个值,每个线程调用countDown将值减1,调用await的线程会阻塞,直到值减为0才继续运行

缺点:count值只能初始化的时候设置一次,无法重新设置

// 构造方法:
public CountDownLatch(int count)

public void await() // 等待
public boolean await(long timeout, TimeUnit unit) // 超时等待
public void countDown() // count - 1
public long getCount() // 获取当前还有多少count

7.4 CyclicBarrier

多个线程相互等待对方完成某一件事,全部线程完成后,这些线程才能继续执行

具体地:初始化设置一个值,每个线程调用await会阻塞,直到调用await阻塞的线程数目达到这个值,屏障打开,所有线程继续工作

CyclicBarrier可以通过rest重置屏障

当线程在等待过程中,屏障被破坏,会抛出异常,通过isBroken检查

  • 已有线程处于等待,调用reset方法,抛出BrokenBarrierException
  • 等待的线程被中断,抛出BrokenBarrierException,并传播到所有线程
  • 执行屏障操作过程中发生异常,异常传播到其他线程,其他线程抛出BrokenBarrierException
  • 超出指定指定等待时间,当前线程抛出TimeoutException,其他线程抛出BrokenBarrierException

7.5 Phaser

与CyclicBarrier的不同之处在于

在使用过程中可以动态增减屏障阻挡得数量

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值