Java并发之ReentrantLock、CountDownLatch、CyclicBarrier、Semaphore

AQS

AQS(AbstractQueuedSynchronizer):抽象队列同步器,是一种用来构建锁和同步器的框架。在是JUC下一个重要的并发类,例如:ReentrantLock、Semaphore、CountDownLatch、LimitLatch等并发都是由AQS衍生出来的。

理解

CLH队列

是一种基于链表的可扩展,高性能,公平的自旋锁。它的队列中每个节点等待前驱节点释放锁,当前置节点执行完成,才会唤醒后置节点,这样最多只有后置节点和新进入的线程(非公平锁状态下)来抢占CPU资源,其余线程处于阻塞状态,极大地减少了CPU开销(同时将多个线程唤醒时,唤醒的线程将进入RUNABLE状态,但是只有一个线程会从竞争获取到锁,而其他线程将会处于BLOCKED状态)。

CLH的出列

当node1出列时,就会把当head的指向出队的下个节点node2,同时也把node1的上下关系断开。
在这里插入图片描述

CLH的入列

队列初始化的时候,head和tail都为空。此时有节点入列,这把当前节点存放到队列尾部节点的下个节点中,在让tail指向这个新增节点,同时也会处理入队的节点的上下节点关系。
在这里插入图片描述

CAS

全称为Compare-And-Swap,它是一条CPU并发原语,也是一种乐观锁(类似数据库设计表时添加的version字段)。执行过程:如果当前状态值(内存中在stateOffset位置的值)等于预期值(expect),则自动将同步状态设置为给定的更新值(update);反之则重试(好像是10次),重试后依旧无果,那么就更新失败。

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        // stateOffset指向一段内存,通过这个可以准确地告诉你某个字段相对于对象的起始内存地址的字节偏移,便于比较。
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

过程

  • 抢锁
    公平锁
    按照线程锁的添加顺序(由CLH来保证)来获取锁。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,添加到队列中
            selfInterrupt();// 添加到队列成功后,就进行中断状态
    }
    
    // 返回false时,获取锁失败,返回true,获取成功
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取锁的状态state
        if (c == 0) {
            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;
    }
    

    非公平锁
    等待队列中的首位正要获取的锁的线程(没有添加到队列中),有同等的机会获取锁。

    final void lock() {
        if (compareAndSetState(0, 1))//尝试修改锁的状态
            setExclusiveOwnerThread(Thread.currentThread());// 修改成功,就把自己设置成独占访问权限
        else
            acquire(1); // 就加入等待队列中
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);// 已非公平锁的方式获取锁,
    }
    // 
    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;
            }
    
  • 释放锁

  • 入队、阻塞

      // 线程入列
      final boolean acquireQueued(final Node node, int arg) {
          boolean failed = true;
          try {
              boolean interrupted = false;
              for (;;) {
                  final Node p = node.predecessor(); // 节点的前面的节点
                  if (p == head && tryAcquire(arg)) {// 如果是前置节点是头结点,就再次尝试获取锁
                      setHead(node);
                      p.next = null; // help GC
                      failed = false;
                      return interrupted;
                  }
                  if (shouldParkAfterFailedAcquire(p, node) && // 如果获取锁失败,修改线程状态
                      parkAndCheckInterrupt())
                      interrupted = true;// 线程状态
              }
          } finally {
              if (failed) // 当前添加的节点获取到锁时,取消正在尝试获取的节点
                  cancelAcquire(node);
          }
      }
      // 判断节点状态,用于判断是否可以中断,同时处理掉线程中已经中断的节点
      private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)// 正常阻塞的节点 SIGNAL=-1
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {// 节点状态错误,删除掉这样的节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);// 将前置节点修改位-1,表示后续节点将被阻塞。
        }
        return false;
    }
    
    // 队列是否有前置线程   
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
        // h = t时,队列中至少有两个队列(未初始化的时候,h,t都为null,说明没有前执行的线程,返回false;如多队列中只有一个线程的时候,h=t, 这个线程一定正在执行,返回false;队列中存在多个时,
        // h != t时,(s = h.next) == null 这个条件可能是为了在end()中,给head设置new Node时,还未将head赋值给tail时做的处理,s.thread != Thread.currentThread()用于判断是不是自旋)
    }
    
    // 添加到队列中
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {// 尾节点不是空花,就添加到最后
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {// 把当前节点设置成尾节点
                pred.next = node;// 把原来的未节点的下分节点设置成当前添加的节点
                return node;
            }
        }
        enq(node);// 兜底的手段:1)一定要把当前节点添加到队列中 2)对位未初始化,初始化队列中
        return node;
    }
    // 通过死循环添加到队列中
     private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))// 头节未nul时,增加一个空的新节点
                    tail = head;// 让尾节点等于头节点
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {// 把当前节点添加到未节点
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
  • 唤醒

// 释放锁
public void unlock() {
    sync.release(1);
}
// 释放锁的具体实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {// 释放成功后,
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒当前的头结点
        return true;
    }
    return false;
}
// 尝试释放
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())// 释放锁的线程和获取锁的线程不同,抛出异常
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {// 为0是,说明锁处于空闲状态
        free = true;
        setExclusiveOwnerThread(null);// 修改当前获取锁的线程为空
    }
    setState(c);// 修改锁的状态
    return free;// c != 0时,说明自选还未完成,释放锁失败。
}

// 唤醒下个节点
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)// 节点的线程状态处于等待中,就更新为0.
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {// 线程可能超时,或者中断了,无法在唤醒了
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)// 从队列尾部开始唤醒可以获取锁的线程,直到上一个完成的线程(t).这里从尾部开始的原因可能是越早的线程越容易超时(中断),所以从尾部获取比较快速。
            if (t.waitStatus <= 0)// 线程状态必须<=0。由于线程在由初始化0变成-1时,不是一个原子,所以这里位<=0
                s = t;
    }
    if (s != null)//  当线程存在时,唤醒线程
        LockSupport.unpark(s.thread);
}
  • 出列
    在添加队列的时候调用acquireQueued时,会将head出列,交给gc回收
    在这里插入图片描述

机制

  • state
    锁的状态:
    • 0:锁处于空闲状态
    • >=1: 已经有线程获取到锁了(大于1 自选)
	 /**
	  * The synchronization state.
	  */
	 private volatile int state;
  • waitState
    线程状态:
    • -1:表示该节点的后继节点被阻塞(或即将被阻塞),因此当前节点在释放或取消时必须解除其后继节点的阻塞。
    • 0:初始化的状态
    • -2:条件状态(暂时不清楚)
    • >=1:表示由于超时或中断,此节点被取消,节点永远不会离开这种状态。取消了节点的线程不会再阻塞其它线程。

CountDownLatch

A线程需要等待一定数量其他线程执行完成后,才继续A线程下一步。

public class CountDownLatchExample {
    // 需要等待的线程数据
    public static final CountDownLatch latch = new CountDownLatch(4);
    public static volatile int data = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 10; i++) {
                    data = i;
                    latch.countDown();// 执行4次之后
                    Tools.randomPause(1000);
                }
            }
        });

        thread.start();
        latch.await(); // 等待thread的run中for虚幻执行四次后,线程就会结束。
        Debug.info("It's done, data=%d", data);
    }
}

// 结果
[2024-03-15 14:44:07.419][INFO][main]:It's done, data=4 

说明:

// 构造类设置完成countDown的次数
public CountDownLatch(int count) {
	if (count < 0) throw new IllegalArgumentException("count < 0");
	this.sync = new Sync(count);
}

Sync(int count) {
    setState(count);
}

// AQS中同步器的状态,当设置为4时,此时 state =4
private volatile int state;

// latch.await()方法
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
// await()最终调用doAcquireSharedInterruptibly(),把当前线程添加到
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED); // 把当前线程添加到AQS中,addWaiter里面的逻辑:如果队列的尾节点为空说明没有初始化,
    //那就初始化队列调用addWaiter.end()方法,并且添加到同步队列中,此时队列的tail和head都是指向node。
    boolean failed = true;
    try {
        for (;;) {// 当前线程一直等待
            final Node p = node.predecessor(); // 节点的前节点
            if (p == head) {// 当前线程所在节点是否是头节点
                int r = tryAcquireShared(arg);// 获取锁
                if (r >= 0) {// countdown都执行完成后,r为-1,此时获取锁成功了
                    setHeadAndPropagate(node, r);// 修改node状态,并处理队列中node状态(此时只有一个队列中只有一个node),并且唤醒线程,继续执行(doReleaseShared并没有完全看懂!!)
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())// 能到这里说明countdown执行的次数还不等于state值,继续处理其他的node和检查中断状态
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}



// countDown方法
public void countDown() {
   sync.releaseShared(1);
}
// countDown中调用的releaseShared方法
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

// 每当完成一次countDown时,调用tryReleaseShared方法,把同步器状态减1
protected boolean tryReleaseShared(int releases) {
    // Decrement count; 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;
    }
}

// 当同步器的状态等于0时,执行这个代码,在这个CountDownLatch似乎没有用到
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;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值