并发编程-AQS 共享锁实现原理

AQS共享锁的实现原理以Semaphore为例

Semaphore

控制访问特定资源的线程数目(permits)。可用场景:资源访问、服务限流。

//构造方法,默认fair为false,即非公平锁。
Semaphore(int permits)
Semaphore(int permits, boolean fair)

获取资源

  1. Semaphore初始化的时候就将state的值设置为permits,默认为非公平锁。
  2. 当有线程尝试获取锁时,会使用死循环(高并发下进行大量CAS会有失败的情况)将state-1表示可用数量,如果可用数量大于0,则进行CAS操作,将state设置为可用数量。
  3. 当超过permits数量的线程池尝试获取锁时,此时可用数量将会小于0,此时将会返回一个负数的可用数量。
    相关源码赏析
final int nonfairTryAcquireShared(int acquires) {
          for (;;) {
              int available = getState();
              int remaining = available - acquires;
              if (remaining < 0 ||
                  compareAndSetState(available, remaining))
                  return remaining;
          }
      }
  1. 当可用数量小于0时,会将线程以共享(Node.SHARED)的方式插入到CLH队列中。第一个入队的线程会先创建一个空的Node,且尾部节点等于头部节点,后续入队的线程会将当前线程节点的prev指向上一个的tail,并用CAS操作将上一个的tail指向当前节点,相关源码在java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly方法中。
  2. Node初始化时状态为0,入队后会通过java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire方法移除cancel状态的节点,并通过CAS操作将当前节点状态置为signal。之后续会将此线程阻塞。

释放资源

  1. 与上面相反,state会加上释放资源的数量,并通过CAS操作更新state。
  2. 将CLH头节点由SIGNAL状态更改为0,并将头结点下一个节点状态不为CANCEL状态的节点唤醒。让其去竞争锁资源。注意:如果头结点本身的状态为0,会将其先设置为PROPAGATE(传播)状态,意味着状态需向后一个节点传播。

相关源码赏析

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

CountDownLatch

使一个线程等待其他线程各自执行完毕后再执行。比如:和朋友出去玩时,一个人去景区买票,一个人买零食,集合后再一起进景区。

工作原理

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

使用示例

public static void main(String[] args) throws InterruptedException {
        long st = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(2);
        //假设业务1执行3s
        new Thread(new Task1(countDownLatch)).start();
        //假设业务2执行5s
        new Thread(new Task2(countDownLatch)).start();
        countDownLatch.await();
        log.info("all task executed in {} millSeconds, we can start next task", System.currentTimeMillis() - st);
    }

结果验证:
在这里插入图片描述

CyclicBarrier

CountDownLatch类似,不同的是CyclicBarrier没有主次之分

CountDownLatch可以看成是单个任务做完后进行汇总,而CyclicBarrier是所有任务做完后一起汇总。

注意:如果CyclicBarrier构造函数中的parties的值大于任务数,会一直等待。

Executors

线程初始化工具类

Exchanger

线程间数据交换。

你可能感兴趣的:(Java架构师沿途风景,Java,java,semaphore)