七、JUC并发工具

news2025/1/16 20:56:52

文章目录

  • JUC并发工具
  • CountDownLatch应用&源码分析
    • CountDownLatch介绍
    • CountDownLatch应用
    • CountDownLatch源码分析
      • 有参构造
      • await方法
      • countDown方法
  • CyclicBarrier应用&源码分析
    • CyclicBarrier介绍
    • CyclicBarrier应用
    • CyclicBarrier源码分析
      • CyclicBarrier的核心属性
      • CyclicBarrier的有参构造
      • CyclicBarrier中的await方法
  • Semaphone应用&源码分析
    • Semaphore介绍
    • Semaphore应用
    • Semaphore源码分析
      • Semaphore的整体结构
      • Semaphore的非公平的获取资源
      • Semaphore公平实现
      • Semaphore释放资源
    • AQS中PROPAGATE节点
      • 掌握JDK1.5-Semaphore执行流程图
      • 分析JDK1.8的变化

JUC并发工具

CountDownLatch应用&源码分析

CountDownLatch介绍

CountDownLatch就是JUC包下的一个工具,整个工具最核心的功能就是计数器。
如果有三个业务需要并行处理,并且需要知道三个业务全部都处理完毕了。
需要一个并发安全的计数器来操作。
CountDownLatch就可以实现。
给CountDownLatch设置一个数值。可以设置3。
每个业务处理完毕之后,执行一次countDown方法,指定的3每次在执行countDown方法时,对3进行-1。
主线程可以在业务处理时,执行await,主线程会阻塞等待任务处理完毕。
当设置的3基于countDown方法减为0之后,主线程就会被唤醒,继续处理后续业务。
在这里插入图片描述
当咱们的业务中,出现2个以上允许并行处理的任务,并且需要在任务都处理完毕后,再做其他处理时,可以采用CountDownLatch去实现这个功能。

CountDownLatch应用

模拟有三个任务需要并行处理,在三个任务全部处理完毕后,再执行后续操作。
CountDownLatch中,执行countDown方法,代表一个任务结束,对计数器 -1。
执行await方法,代表等待计数器变为0时,再继续执行。
执行await(time,unit)方法,代表等待time时长,如果计数器不为0,返回false,如果在等待期间,计数器为0,方法就返回true
一般CountDownLatch更多的是基于业务去构建,不采用成员变量。

public class TestCountDownLatch {
    static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
    static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主业务开始执行");
        sleep(1000);
        executor.execute(TestCountDownLatch::a);
        executor.execute(TestCountDownLatch::b);
        executor.execute(TestCountDownLatch::c);
        System.out.println("三个任务并行执行,主业务线程等待");
        // 死等任务结束
        // countDownLatch.await();
        // 如果在规定时间内,任务没有结束,返回false
        if (countDownLatch.await(10, TimeUnit.SECONDS)) {
            System.out.println("三个任务处理完毕,主业务线程继续执行");
        } else {
            System.out.println("三个任务没有全部处理完毕,执行其他的操作");
        }
    }

    private static void a() {
        System.out.println("A任务开始");
        sleep(1000);
        System.out.println("A任务结束");
        countDownLatch.countDown();
    }

    private static void b() {
        System.out.println("B任务开始");
        sleep(1500);
        System.out.println("B任务结束");
        countDownLatch.countDown();
    }

    private static void c() {
        System.out.println("C任务开始");
        sleep(2000);
        System.out.println("C任务结束");
        countDownLatch.countDown();
    }

    private static void sleep(long timeout) {
        try {
            Thread.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
    }
}

CountDownLatch源码分析

保证CountDownLatch就是一个计数器,没有什么特殊的功能,查看源码也只是查看计数器实现的方式。
发现CountDownLatch的内部类Sync继承了AQS,CountDownLatch就是基于AQS实现的计数器。
AQS就是一个state属性,以及AQS双向链表。
猜测计数器的数值实现就是基于state去玩的。
主线程阻塞的方式,也是阻塞在了AQS双向链表中。

有参构造

就是构建内部类Sync,并且给AQS中的state赋值。

// CountDownLatch的有参构造
public CountDownLatch(int count) {
	// 健壮性校验
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 构建内部类,Sync传入count
    this.sync = new Sync(count);
}

// AQS子类,Sync的有参构造
Sync(int count) {
	// 就是给AQS中的state赋值
    setState(count);
}

await方法

await方法就是判断当前CountDownLatch中的state是否为0,如果为0,直接正常执行后续任务。
如果不为0,以共享锁的方式,插入到AQS的双向链表,并且挂起线程。

// 一般主线程await的方法,阻塞主线程,等待state为0
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 判断线程是否中断,如果中断标记位是true,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        // 共享锁挂起的操作
        doAcquireSharedInterruptibly(arg);
}

// tryAcquireShared在CountDownLatch中的实现
protected int tryAcquireShared(int acquires) {
	// 查看state是否为0,如果为0,返回1,不为0,返回-1
    return (getState() == 0) ? 1 : -1;
}

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 封装当前先成为Node,属性为共享锁
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 在这,就需要挂起当前线程。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

countDown方法

countDown方法本质就是对state - 1,如果state - 1后变为0,需要去AQS的链表中唤醒挂起的节点。

// countDown对计数器-1
public void countDown() {
	// 是-1。
    sync.releaseShared(1);
}

// AQS提供的功能
public final boolean releaseShared(int arg) {
    // 对state - 1
    if (tryReleaseShared(arg)) {
        // state - 1后,变为0,执行doReleaseShared
        doReleaseShared();
        return true;
    }
    return false;
}

// CountDownLatch的tryReleaseShared实现
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    // 死循环是为了避免CAS并发问题
    for (;;) {
        // 获取state
        int c = getState();
        // state已经为0,直接返回false
        if (c == 0)
            return false;
        // 对获取到的state - 1
        int nextc = c-1;
        // 基于CAS的方式,将值赋值给state
        if (compareAndSetState(c, nextc))
            // 赋值完,发现state为0了。此时可能会有线程在await方法处挂起,那边挂起,需要这边唤醒
            return nextc == 0;
    }
}

// 如何唤醒在await方法处挂起的线程
private void doReleaseShared() {
    for (;;) {
    	// 拿到head
        Node h = head;
         // head不为null,有值,并且head != tail,代表至少2个节点 
         // 一个虚拟的head,加上一个实质性的Node
        if (h != null && h != tail) {
            // 说明AQS队列中有节点
            int ws = h.waitStatus;
            // 如果head节点的状态为 -1.
            if (ws == Node.SIGNAL) {
                // 先对head节点将状态从-1,修改为0,避免重复唤醒的情况
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 正常唤醒节点即可,先看head.next,能唤醒就唤醒,如果head.next有问题,从后往前找有效节点
                unparkSuccessor(h);
            }
            // 会在Semaphore中谈到这个位置
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 会在Semaphore中谈到这个位置
        if (h == head)                   // loop if head changed
            break;
    }
}

CyclicBarrier应用&源码分析

CyclicBarrier介绍

从名字上来看CyclicBarrier,就是代表循环屏障。

Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当达到一个线程阻塞在屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务。

Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。

CyclicBarrier一般被称为栅栏。

CyclicBarrier是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于await方法在屏障点阻塞。

CyclicBarrier并没有基于AQS实现,他是基于ReentrantLock锁的机制去实现了对屏障点–,以及线程挂起的操作。(CountDownLatch本身是基于AQS,对state进行release操作后,可以-1)

CyclicBarrier没来一个线程执行await,都会对屏障数值进行-1操作,每次-1后,立即查看数值是否为0,如果为0,直接唤醒所有的互相等待线程。

CyclicBarrier对比CountDownLatch区别

  • 底层实现不同。CyclicBarrier基于ReentrantLock做的。CountDownLatch直接基于AQS做的。
  • 应用场景不同。CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器。CyclicBarrier可以实现相比CountDownLatch更复杂的业务,执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
  • CyclicBarrier还提供了很多其他的功能:
    • 可以获取到阻塞的现成有多少
    • 在线程互相等待时,如果有等待的线程中断,可以抛出异常,避免无限等待的问题。
  • CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程 也一起计数和等待,等待的线程达到数值后,再统一唤醒

CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再一次执行。

CyclicBarrier应用

出国旅游,导游小姐姐需要等待所有乘客都到位后,发送护照,签证等等文件,再一起出发。
比如Tom,Jack,Rose三个人组个团出门旅游。
在构建CyclicBarrier可以指定barrierAction,可以选择性指定,如果指定了,那么会在barrier归0后,优先执行barrierAction任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务。
所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒。
如果在等待期间,有线程中断了,唤醒所有线程后,CyclicBarrier无法继续使用。
如果线程中断后,需要继续使用当前的CyclicBarrier,需要调用reset方法,让CyclicBarrier重置。
如果CyclicBarrier的屏障数值到达0之后,他默认会重置屏障数值,CyclicBarrier在没有线程中断时,是可以重复使用的。

public static void main(String[] args) throws InterruptedException {
    CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        System.out.println("等到各位大佬都到位之后,分发护照和签证等内容!");
    });
    new Thread(() -> {
        System.out.println("Tom到位!!!");
        try {
            barrier.await();
        } catch (Exception e) {
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Tom出发!!!");
    }).start();

    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Jack到位!!!");
        try {
            barrier.await();
        } catch (Exception e) {
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Jack出发!!!");
    }).start();

    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Rose到位!!!");
        try {
            barrier.await();
        } catch (Exception e) {
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Rose出发!!!");
    }).start();
    /* 
        tom到位,jack到位,rose到位 导游发签证 tom出发,jack出发,rose出发
     */
}

CyclicBarrier源码分析

分成两块内容去查看,首先查看CyclicBarrier的一些核心属性,然后再查看CyclicBarrier的核心方法。

CyclicBarrier的核心属性

public class CyclicBarrier {
    // 这个静态内部类是用来标记是否中断的
    private static class Generation {
        boolean broken = false;
    }
    /** CyclicBarrier是基于ReentrantLock实现的互斥操作,以及计数原子性操作 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 基于当前的Condition实现线程的挂起和唤醒 */
    private final Condition trip = lock.newCondition();
    /** 记录有参构造传入的屏障数值,不会对这个数值做操作 */
    private final int parties;
    /** 当屏障数值达到0之后,优先执行当前任务 */
    private final Runnable barrierCommand;
    /** 初始化默认的Generation,用来标记线程中断情况 */
    private Generation generation = new Generation();
    /** 每来一个线程等待,就对count进行-- */
    private int count;
}

CyclicBarrier的有参构造

掌握构建CyclicBarrier之后,内部属性的情况

// 这个是CyclicBarrier的有参构造
// 在内部传入了parties,屏障点的数值
// 还传入了barrierAction,屏障点的数值达到0,优先执行barrierAction任务
public CyclicBarrier(int parties, Runnable barrierAction) {
    // 健壮性判
    if (parties <= 0) throw new IllegalArgumentException();
    // 当前类中的属性parties是保存屏障点数值的
    this.parties = parties;
    // 将parties赋值给属性count,每来一个线程,继续count做-1操作。
    this.count = parties;
    // 优先执行的任务
    this.barrierCommand = barrierAction;
}

CyclicBarrier中的await方法

在CyclicBarrier中,提供了2个await方法。

  • 第一个是无参的方式,线程要死等,直屏障点数值为0,或者有线程中断。
  • 第二个是有参方式,传入等待的时间,要么时间到位了,要不就是直屏障点数值为0,或者有线程中断。

无论是哪种await方法,核心都在于内部调用的dowait方法。
dowait方法主要包含了线程互相等待的逻辑,以及屏障点数值到达0之后的操作。

Semaphone应用&源码分析

Semaphore介绍

sync,ReentrantLock是互斥锁,保证一个资源同一时间只允许被一个线程访问。
Semaphore(信号量)保证1个或多个资源可以被指定数量的线程同时访问。
底层实现是基于AQS去做的。
Semaphore底层也是基于AQS的state属性做一个计数器的维护。state的值就代表当前共享资源的 个数。如果一个线程需要获取的1或多个资源,直接查看state的标识的资源个数是否足够,如果足够的,直接对state - 1拿到当前资源。如果资源不够,当前线程就需要挂起等待。知道持有资源的线程释放资源后,会归还给Semaphore中的state属性,挂起的线程就可以被唤醒。
Semaphore也分为公平和非公平的概念。
使用场景:连接池对象就可以基础信号量去实现管理。在一些流量控制上,也可以采用信号量去实现。再比如去迪士尼或者是环球影城,每天接受的人流量是固定的,指定一个具体的人流量,可能接 受10000人,每有一个人购票后,就对信号量进行–操作,如果信号量已经达到了0,或者是资源不足,此时就不能买票。

Semaphore应用

以上面环球影城每日人流量为例子去测试一下。

public static void main(String[] args) throws InterruptedException {
    // 今天环球影城还有人个人流量
    Semaphore semaphore = new Semaphore(10);
    new Thread(() -> {
        System.out.println("一家三口要去~~");
        try {
            semaphore.acquire(3);
            System.out.println("一家三口进去了~~~");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("一家三口走了~~~");
            semaphore.release(3);
        }
    }).start();
    for (int i = 0; i < 7; i++) {
        int j = i;
        new Thread(() -> {
            System.out.println(j + "大哥来了。");
            try {
                semaphore.acquire();
                System.out.println(j + "大哥进去了~~~");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(j + "大哥走了~~~");
                semaphore.release();
            }
        }).start();
    }
    Thread.sleep(10);
    System.out.println("main大哥来了。");
    if (semaphore.tryAcquire()) {
        System.out.println("main大哥进来了。");
    } else {
        System.out.println("资源不够,main大哥进来了。");
    }
    Thread.sleep(10000);
    System.out.println("main大哥又来了。");
    if (semaphore.tryAcquire()) {
        System.out.println("main大哥进来了。");
        semaphore.release();
    } else {
        System.out.println("资源不够,main大哥进来了。");
    }
}

其实Semaphore整体就是对构建Semaphore时,指定的资源数的获取和释放操作。
获取资源方式:

  • acquire():获取一个资源,没有资源就挂起等待,如果中断,直接抛异常
  • acquire(int):获取指定个数资源,资源不够,或者没有资源就挂起等待,如果中断,直接抛异常
  • tryAcquire():获取一个资源,没有资源返回false,有资源返回true tryAcquire(int):获取指定个数资源,没有资源返回false,有资源返回true
  • tryAcquire(time,unit):获取一个资源,如果没有资源,等待time.unit,如果还没有,就返回 false
  • tryAcquire(int,time,unit):获取指定个数资源,如果没有资源,等待time.unit,如果还没有,就返回false
  • acquireUninterruptibly():获取一个资源,没有资源就挂起等待,中断线程不结束,继续等
  • acquireUninterruptibly(int):获取指定个数资源,没有资源就挂起等待,中断线程不结束,继续等

归还资源方式:

  • release():归还一个资源
  • release(int):归还指定个数资源

Semaphore源码分析

先查看Semaphore的整体结构,然后基于获取资源,以及归还资源的方式去查看源码。

Semaphore的整体结构

Semaphore内部有3个静态内类。
首先是向上抽取的Sync。
其次还有两个Sync的子类NonFairSync以及FairSync两个静态内部类。
Sync内部主要提供了一些公共的方法,并且将有参构造传入的资源个数,直接基于AQS提供的setState方法设置了state属性。
NonFairSync以及FairSync区别就是tryAcquireShared方法的实现是不一样。

Semaphore的非公平的获取资源

在构建Semaphore的时候,如果只设置资源个数,默认情况下是非公平。
如果在构建Semaphore,传入了资源个数以及一个boolean时,可以选择非公平还是公平。

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

从非公平的acquire方法入手

首先确认默认获取资源数是1个,并且acquire是允许中断线程时,抛出异常的。获取资源的方式, 就是直接用state - 需要的资源数,只要资源足够,就CAS的将state做修改。如果没有拿到锁资源, 就基于共享锁的方式去将当前线程挂起在AQS双向链表中。如果基于doAcquireSharedInterruptibly拿锁成功,会做一个事情。会执行setHeadAndPropagate方法。

// 信号量的获取资源方法(默认获取一个资源)
public void acquire() throws InterruptedException {
	// 跳转到了AQS中提供共享锁的方法
    sync.acquireSharedInterruptibly(1);
}

// AQS提供的
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 判断线程的中断标记位,如果已经中断,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 先看非公平的tryAcquireShared实现。
	// tryAcquireShared:
	// 返回小于0,代表获取资源失败,需要排队。
	// 返回大于等于0,代表获取资源成功,直接执行业务代码
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

// 信号量的非公平获取资源方法
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    	// 获取state的数值,剩余的资源个数
        int available = getState();
        // 剩余的资源个数 - 需要的资源个数
        int remaining = available - acquires;
        // 如果-完后,资源个数小于0,直接返回这个负数
        if (remaining < 0 ||
        	// 说明资源足够,基于CAS的方式,将state从原值,改为remaining
            compareAndSetState(available, remaining))
            return remaining;
    }
}

// 获取资源失败,资源不够,当前线程需要挂起等待
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 构建Node节点,线程和共享锁标记,并且到AQS双向链表中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 拿到上一个节点
            final Node p = node.predecessor();
            // 如果是head.next,就抢一手
            if (p == head) {
                // 再次基于非公平的方式去获取一次资源
                int r = tryAcquireShared(arg);
                // 到这,说明拿到了锁资源
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 如果上面没拿到,或者不是head的next节点,将前继节点的状态改为-1,并挂起当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 如果线程中断会抛出异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

acquire()以及acquire(int)的方式,都是执行acquireSharedInterruptibly方法去尝试获取资源,区别只在于是否传入了需要获取的资源个数。
tryAcquire()以及tryAcquire(int因为这两种方法是直接执行tryAcquire,只使用非公平的实现,只有非公平的情况下,才有可能在有线程排队的时候获取到资源。

但是tryAcquire(int,time,unit)这种方法是正常走的AQS提供的acquire。因为这个tryAcquire可以排队一会,即便是公平锁也有可能拿到资源。这里的挂起和acquire挂起的区别仅仅是挂起的时间问题。

  • acquire是一直挂起直到线程中断,或者线程被唤醒。
  • tryAcquire(int,time,unit)是挂起一段时间,直到线程中断,要么线程被唤醒,要么阻塞时间到了。

还有acquireUninterruptibly()以及acquireUninterruptibly(int)只是在挂起线程后,不会因为线程 的中断而去抛出异常

Semaphore公平实现

公平与非公平只是差了一个方法的实现tryAcquireShared实现。
这个方法的实现中,如果是公平实现,需要先查看AQS中排队的情况。

// 信号量公平实现
protected int tryAcquireShared(int acquires) {
    for (;;) {
    	// 公平实现在走下述逻辑前,先判断队列中排队的情况
		// 如果没有排队的节点,直接不走if逻辑
		// 如果有排队的节点,发现当前节点处在head.next位置,直接不走if逻辑
        if (hasQueuedPredecessors())
            return -1;
        // 下面这套逻辑和公平实现是一模一样的。
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

Semaphore释放资源

因为信号量从头到尾都是共享锁的实现…
释放资源操作,不区分公平和非公平。

// 信号量释放资源的方法入口
public void release() {
    sync.releaseShared(1);
}

// 释放资源不分公平和非公平,都走AQS的releaseShared
public final boolean releaseShared(int arg) {
	// 优先查看tryReleaseShared,这个方法是信号量自行实现的。
    if (tryReleaseShared(arg)) {
        // 只要释放资源成功,执行doReleaseShared,唤醒AQS中排队的线程,去竞争Semaphore的资源
        doReleaseShared();
        return true;
    }
    return false;
}

// 信号量实现的释放资源方法
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // 拿到当前的state
        int current = getState();
        // 将state + 归还的资源个数,新的state要被设置为next
        int next = current + releases;
        // 如果归还后的资源个数,小于之前的资源数。
		// 避免出现归还资源后,导致next为负数,需要做健壮性判断
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // CAS操作,保证原子性,只会有一个线程成功的就之前的state修改为next
        if (compareAndSetState(current, next))
            return true;
    }
}

AQS中PROPAGATE节点

为了更好的了解PROPAGATE节点状态的意义,优先从JDK1.5去分析一下释放资源以及排队后获取资源的后置操作。

掌握JDK1.5-Semaphore执行流程图

首先查看4个线程获取信号量资源的情况
在这里插入图片描述
往下查看释放资源的过程会触发什么问题。
首先t1释放资源,做了进一步处理。
在这里插入图片描述
当线程3获取锁资源后,线程2再次释放资源,因为执行点问题,导致线程4无法被唤醒。

分析JDK1.8的变化

在这里插入图片描述

//====================================JDK1.5实现================================ 
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

private void setHeadAndPropagate(Node node, int propagate) {
    setHead(node);
    if (propagate > 0 && node.waitStatus != 0) {
        Node s = node.next;
        if (s == null || s.isShared()) unparkSuccessor(node);
    }
}

//====================================JDK1.8实现================================
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

private void doReleaseShared() {
    for (;;) {
        // 拿到head节点
        Node h = head;
        // 判断AQS中有排队的Node节点
        if (h != null && h != tail) {
            // 拿到head节点的状态
            int ws = h.waitStatus;
            // 状态为-1
            if (ws == Node.SIGNAL) {
                // 将head节点的状态从-1,改为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后继节点
                unparkSuccessor(h);
            }
            // 发现head状态为0,将head状态从0改为-3,目的是为了往后面传播
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 没有并发的时候。head节点没变化,正常完成释放排队的线程
        if (h == head)                   // loop if head changed
            break;
    }
}

private void setHeadAndPropagate(Node node, int propagate) {
    // 拿到head
    Node h = head; // Record old head for check below
    // 将线程3的Node设置为新的head
    setHead(node);
    // 如果propagate 大于0,代表还有剩余资源,直接唤醒后续节点,如果不满足,也需要继续往后判断看下是否需要传播
    // h == null:看成健壮性判断即可
	// 之前的head节点状态为负数,说明并发情况下,可能还有资源,需要继续向后唤醒Node
	// 如果当前新head节点的状态为负数,继续释放后续节点
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        // 唤醒当前节点的后继节点
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/378868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

echarts实现知识图谱,生产项目

echarts实现知识图谱&#xff0c;生产项目内容简介效果演示代码逻辑结束语内容简介 在实际生产项目中&#xff0c;需要对后端知识数据进行展示。需求如下&#xff1a; 点击节点可以展示与此节点相关的节点信息右键点击节点可以对节点的信息进行修改悬浮在节点上可以查看节点的…

通用人工智能(AGI):人工智能的下一个阶段

除了人工智能(AI)的改进和新应用之外&#xff0c;大多数人都认为&#xff0c;当通用人工智能(AGI)出现时&#xff0c;人工智能的下一次飞跃将发生。我们将AGI宽泛地定义为机器或计算机程序理解或学习人类可以完成的任何智力任务的假设能力。然而&#xff0c;对于何时以及如何实…

系统性能测试指标

性能测试的目的 1.评估系统的能力&#xff0c;测试中得到的负荷和响应时间数据可以被用于验证所计划的模型的能力&#xff0c;并帮助作出决策。 2.识别体系中的弱点&#xff1a;受控的负荷可以被增加到一个极端的水平&#xff0c;并突破它&#xff0c;从而修复体系的瓶颈或薄…

leetcode: Swapping Nodes in a Linked List

leetcode: Swapping Nodes in a Linked List1. 题目描述2. 题目解答3. 总结1. 题目描述 You are given the head of a linked list, and an integer k.Return the head of the linked list after swapping the values of the kth node from the beginning and the kth node f…

ECMAScript 详解

ECMAScript 历史我们首先来看 ECMA 是什么。ECMA&#xff0c;是欧洲计算机制造商协会&#xff08;European Computer Manufacturers Association&#xff09;的简称&#xff0c;是一家国际性会员制度的信息和电信标准组织。1994 年之后&#xff0c;由于组织的标准牵涉到很多其他…

【Leetcode 剑指Offer】第 6 天 搜索与回溯算法(简单)

搜索与回溯剑指 Offer 32 - I. 从上到下打印二叉树层序遍历 广搜 BFScollections双端队列 deque剑指 Offer 32 - II. 从上到下打印二叉树 II剑指 Offer 32 - III. 从上到下打印二叉树 III剑指 Offer 32 - I. 从上到下打印二叉树 题;从上到下打印出二叉树的每个节点&#xff0c…

单通道说话人语音分离——DPRNN(Dual-Path Recurrent Neural Network)

参考文献&#xff1a;《DUAL-PATH RNN: EFFICIENT LONG SEQUENCE MODELING FOR TIME-DOMAIN SINGLE-CHANNEL SPEECH SEPARATION》 DPRNN网络是Con-Tasnet的改进网络 Con-Tasnet介绍详情请看上一篇文章 单通道说话人语音分离——Conv-TasNet(Convolutional Time-domain audio…

应用程序性能优化方案,web服务五级缓存优化,服务器性能优化...

winfrom 全局异常捕获WPF 全局异常捕获Asp.Net全局异常捕获MVC 全局异常捕获AspNetNetCore 全局异常捕获一级缓存html/css/js 前端缓存二级缓存Asp.Net MVC AspNetCore 客户端缓存设置三级缓存服务端缓存四级redis 数据库缓存服务端缓存五级sqlserver 数据库缓存设置分布式缓存…

2023年湖北建设厅资料员证怎么考需要什么条件?启程别

2023年湖北建设厅资料员证怎么考需要什么条件&#xff1f;启程别 湖北资料员目前已经开始组织考试了&#xff0c;各位想要考资料员的现在可以开始准备报考相关准备工作了。资料员怎么考需要什么条件&#xff1f;启程别告诉你 建设厅资料员证怎么考 网报&#xff1a;七大员全国…

ARM学习(十八)jinkozone调试问题总结

笔者来聊聊ARM的函数的调用规则 1、ARM函数调用规则介绍首先介绍几个术语&#xff0c; AAPCS&#xff1a;Procedure Call Standard for the ARM Architecture APCS&#xff1a;ARM Procedure Call Standard TPCS&#xff1a;Thumb Procedure Call Standard ATPCS&#xff1a;AR…

5.3 redis分布式锁【Java 面试第三季】

redis分布式锁【Java 面试第三季】前言推荐redis分布式锁概览47_redis分布式锁前情说明48_boot整合redis搭建超卖程序-上49_boot整合redis搭建超卖程序-下50_redis分布式锁0151_redis分布式锁0252_redis分布式锁0353_redis分布式锁0454_redis分布式锁0555_redis分布式锁0656_re…

【Java|golang】2363. 合并相似的物品---桶排序

给你两个二维整数数组 items1 和 items2 &#xff0c;表示两个物品集合。每个数组 items 有以下特质&#xff1a; items[i] [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 &#xff0c;weighti 表示第 i 件物品的 重量 。 items 中每件物品的价值都是 唯一的 。 请你…

报名投票链接怎么做做一个投票的链接怎么做微信投票链接怎么做

近些年来&#xff0c;第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放&#xff0c;更多人选择微信投票小程序平台&#xff0c;因为它有非常大的优势。1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…

案例|政务大数据平台数据安全建设实践

《关于加强数字政府建设的指导意见》、《全国一体化政务大数据体系建设指南》&#xff0c;对全面开创数字政府建设新局面作出部署&#xff0c;保障数据安全&#xff0c;提升数字政府基础设施的支撑能力&#xff0c;也明确成为数字政府建设探索与实践中的重点任务。那么&#xf…

王道计算机网络课代表 - 考研计算机 第五章 传输层 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记&#xff0c;以及一整年里对 计算机网络 知识点的理解的总结。希望对新一届的计算机考研人提供帮助&#xff01;&#xff01;&#xff01; 关于对 “传输层” 章节知识点总结的十分全面&#xff0c;涵括了《计算机网络》课程里的全…

klee内存模型

klee内存模型一.LLVM基础二.Klee中相关的类2.1.基础类2.2.内存管理相关类三.示例3.1.示例13.2.示例23.3.示例33.4.示例4这篇blog主要通过一些简单的示例来了解以下klee对内存的建模方式。 首先一个C语言程序在运行时&#xff0c;内存主要包括&#xff1a; 代码段&#xff0c;程…

如何从零到一的设计一套轻易云数据集成平台这样的系统架构

一个集成平台的架构设计需要考虑多个方面&#xff0c;包括系统架构、技术选型、数据存储、安全设计等。下面是参考轻易云数据集成平台的架构设计思路&#xff1a;系统架构首先需要确定系统的整体架构&#xff0c;这包括前后端分离、微服务架构、容器化部署等。根据需求和规模的…

老字号白酒企业——金徽酒借力泛微,升级门户,实现统一办公

金徽酒股份有限公司前身系康庆坊、万盛魁等多个徽酒老作坊基础上组建的省属国营大型白酒企业&#xff0c;曾用名甘肃陇南春酒厂&#xff0c;是国内建厂最早的中华老字号白酒酿造企业之一。2016年3月10日&#xff0c;金徽酒在上海证券交易所挂牌上市。 &#xff08;图片素材来自…

Airbnb(三) Managing Diversity in Airbnb Search 搜索多样性

abstract 搜索系统中一个长期的问题是结果多样性。从产品角度讲&#xff0c;给用户多种多样的选择&#xff0c;有助于提升用户体验及业务指标。 多样性需求和模型的目标是相矛盾的&#xff0c;因为传统ctr模型是 point wise&#xff0c;只看单个相关性不管相邻之间item差异。 …

字节前端一面常见vue面试题(必备)

Vue为什么没有类似于React中shouldComponentUpdate的生命周期 考点: Vue的变化侦测原理前置知识: 依赖收集、虚拟DOM、响应式系统 根本原因是Vue与React的变化侦测方式有所不同 当React知道发生变化后&#xff0c;会使用Virtual Dom Diff进行差异检测&#xff0c;但是很多组件…