juc-并发工具类源码解析

CountDownLatch

应用

countDownLatch就是使一个线程在其他线程都执行完之后再执行
CountDownLatch提供了一个构造函数,入参是一个int类型的变量;构造函数中,完成的事情是:把入参的值调用setState(int i);方法

public class  CountDownLatchTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 16; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"离开了");
                //countDown()表示减1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        //await会阻塞,只有countDownLatch变为0时,才会执行下面的方法
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t"+"可以关灯");
    }
}

/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=63442:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar:/Volumes/mpy/studyWorkspace/JUC/target/classes:/Volumes/mpy/software/mavenRepository/junit/junit/4.12/junit-4.12.jar:/Volumes/mpy/software/mavenRepository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Volumes/mpy/software/mavenRepository/org/openjdk/jol/jol-core/0.9/jol-core-0.9.jar com.juc.tool.CountDownLatchTest
5	离开了
7	离开了
0	离开了
3	离开了
9	离开了
main	可以关灯
10	离开了
1	离开了
4	离开了
6	离开了
2	离开了
8	离开了
11	离开了
12	离开了
15	离开了
14	离开了
13	离开了

await()

public final void acquireSharedInterruptibly(int arg)
          throws InterruptedException {
    
    
      if (Thread.interrupted())
          throw new InterruptedException();
      // await()方法,会先调用tryAcquireShared方法,这里面的判断逻辑也简单  
      // return (getState() == 0) ? 1 : -1;也就是说,如果state不为0,就调用下面的方法进行排队
      if (tryAcquireShared(arg) < 0)
          doAcquireSharedInterruptibly(arg);
  }
  

  //这个排队的逻辑就不细说了,大致的意思就是:
  private void doAcquireSharedInterruptibly(int arg)
      throws InterruptedException {
    
    
      //1.如果当前线程是AQS队列中第一个排队的线程,就new一个空节点,然后把当前节点加入到空节点后面
      final Node node = addWaiter(Node.SHARED);
      boolean failed = true;
      try {
    
    
          for (;;) {
    
    
              final Node p = node.predecessor();
              //如果当前节点的上一个节点是head,表示当前线程是第一个排队的线程,就尝试加锁;
              // 这里所谓的加锁,就是执行这行代码 return (getState() == 0) ? 1 : -1;
              if (p == head) {
    
    
                  int r = tryAcquireShared(arg);
                  //如果r>0;表示当前state为0,可以执行await()方法对应的线程中的其他业务代码;
                  // 就会把自己设置为头节点(thread设置为null,prev设置为null)
                  if (r >= 0) {
    
    
                      setHeadAndPropagate(node, r);
                      p.next = null; // help GC
                      failed = false;
                      return;
                  }
              }
              //如果加锁失败,就执行这里,将上一个节点的waitStatus设置为-1,然后调用park()方法休眠
              if (shouldParkAfterFailedAcquire(p, node) &&
                  parkAndCheckInterrupt())
                  throw new InterruptedException();
          }
      } finally {
    
    
          if (failed)
              cancelAcquire(node);
      }
  

countDown()

countDown()方法,整体上,我们可以理解为是来将state-1,然后知道该值为0的时候,将阻塞在队列中的线程唤醒,继续执行主线程的逻辑

//就是一个类似于释放锁的过程,只不过,在tryReleaseShared(arg);方法中,是将当前的state -1;
// 然后通过cas,回写到state变量中;这个方法返回的是一个boolean值,boolean值的判断条件是:
//当前state是否为0;如果为0,就返回true;
//如果这里返回true,就唤醒AQS队列中的等待节点(这时候,理论上是countDownLatch.await();
//方法对应的线程在队列中);也就是说,只有在state变为0的时候,才会执行countDownLatch.await()对应的线程中的方法
  public final boolean releaseShared(int arg) {
    
    
      if (tryReleaseShared(arg)) {
    
    
          doReleaseShared();
          return true;
      }
      return false;
  }


/**
* 在调用countDown()方法的时候,会调用到这里:
*  将当前AQS的state值 - 1
*  如果未减1之前,state已经是0了,就直接返回false(表示这时候已经被其他线程唤醒了),阻塞
*  如果 -1之后的值等于0,就返回true,就尝试唤醒阻塞队列中的线程
* @param releases
* @return
*/
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;
    }
}

doReleaseShared()方法我就不贴代码了,直接截图吧
在这里插入图片描述

CyclicBarrier

CyclicBarrier是做减法,在初始化的时候,指定count的阈值和达到阈值之后要执行的代码
每次调用await方法的时候,会将count-1;
如果减1之后的count不为0,就将线程阻塞(休眠);如果调用await()指定了休眠时间(等待时间),就将线程await指定的时间
如果count为0,就执行初始化时,指定的command,然后将所有等待的线程唤醒,重新对count进行赋值,再开始一轮
如果await方法指定了等待时间,那就休眠指定的时间,时间到了之后,线程依旧还在阻塞(count还没有变为0),那就会唤醒所有线程,然后抛出超时的异常

应用

public class CyclicBarrierTest {
    
    
    public static void main(String[] args) {
    
    
        CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
    
    
            System.out.println("可以执行该语句");
        });

        for (int i = 0; i < 15; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName()+"执行第"+temp+"次");
                try {
    
    
                    try {
    
    
//                        cyclicBarrier.await(2L, TimeUnit.SECONDS);
                        cyclicBarrier.await();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

    }
}


0执行第01执行第12执行第23执行第34执行第45执行第5次
可以执行该语句
6执行第67执行第78执行第89执行第910执行第1011执行第11次
可以执行该语句
12执行第1213执行第1314执行第14

初始化

/* parties:只有达到这个值,才会执行barrierAction的代码
* 这里的count是来做计数的,在每次await的时候,是修改count的值,来判断什么时候,执行runnable,再count变为0的时候,就执行,
* 执行完之后,再对count的值进行重新赋值,赋值为parties
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
    
    
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

阻塞:await()

在调用await的时候,无论是否指定了等待时间,都会调用到该方法
如果指定了等待时间,这里入参的timed是true,且nanos就是指定的等待时间
如果没有指定等待时间,这里入参的timed是false,nanos是null

/**
* Main barrier code, covering the various policies.
*
* 这里的timed如果调用await()方法的时候,默认是false
* 如果调用了await的时候,指定了超时时间,timed就是true
*
* 调用await指定超时时效和不指定的区别是:
* 如果指定了:在超过这个时效之后,还是没有被唤醒,就会尝试唤醒所有线程,并且抛出异常
*/
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
TimeoutException {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        final Generation g = generation;

        /**
             * 如果 当前generation被中止 ,就抛出异常
             */
        if (g.broken)
            throw new BrokenBarrierException();

        /**
             * 如果线程被中断:
             * generation.broken 将该属性设置为true
             * 唤醒所有的等待线程
             * 并抛出异常
             */
        if (Thread.interrupted()) {
    
    
            breakBarrier();
            throw new InterruptedException();
        }

        /**
             * 将计数器 - 1
             */
        int index = --count;
        /**
             * 如果await调用的次数达到了设置的阈值,就执行下面的run()方法
             */
        if (index == 0) {
    
      // tripped
            boolean ranAction = false;
            try {
    
    
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                /**
                     * 执行完毕之后,会唤醒所有等待的线程:nextGeneration()
                     * 并且会new 一个generation对象
                     * 并将count计数重新设置为最初设置的阈值
                     *
                     * ranAction为true,表示正常执行完毕, 如果执行业务逻辑的时候,报错,这时候  ranAction应该还是false
                     */
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
    
    
                /**
                     * 如果ranAction为false,表示未未正常执行完逻辑
                     * 这时候:就尝试唤醒所有的线程,确保在任务未执行成功时,将所有线程唤醒
                     */
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        /**
             * 如果-1之后的state不为0,就会进入到这里的for循环
             */
        for (;;) {
    
    
            try {
    
    
                /**
                     * 如果调用await()时,指定了时间,这里time的就是true;
                     * 如果没有指定超时时间,这里就是false,就会一直等待,直到阈值达到指定的数量时,唤醒所有阻塞的线程
                     *
                     * 如果指定了超时时间,那就等待响应的时间
                     */
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
    
    
                /**
                     * 如果在等待的过程中发生了异常
                     * 那就尝试唤醒所有的线程
                     */
                if (g == generation && ! g.broken) {
    
    
                    breakBarrier();
                    throw ie;
                } else {
    
    
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            /**
                 * 如果generation已经创建了一个新的,就返回index
                 */
            if (g != generation)
                return index;

            /**
                 * 如果设置了超时时间并且达到了超时时效,就停止CyclicBarrier,并且唤醒所有等待的线程
                 * 并抛出异常
                 */
            if (timed && nanos <= 0L) {
    
    
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
    
    
        lock.unlock();
    }
}

Semaphore

semaphore是信号量,表示控制线程数量的,可以简单这样理解
类似于停车场抢占车位,停车场有三个车位,10辆车来了,前三辆会先进行占用,后面7辆会阻塞排队,等前三辆,出来一辆,后面排队的就会进入一个
Semaphore是基于AQS的共享锁实现的,初始化的时候,指定入参的数值,表示同一时间可以访问共享资源的线程数,内部是通过AQS的state变量来控制的

应用

public class SemaphoreTest {
    
    
    public static void main(String[] args) {
    
    
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 7; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t" + "获取到了资源");
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

0	获取到了资源
2	获取到了资源
1	获取到了资源

3	获取到了资源
5	获取到了资源
4	获取到了资源

6	获取到了资源

初始化

首先,Semaphore是基于AQS的共享锁实现的,初始化的时候,指定入参的数值,表示同一时间可以访问共享资源的线程数,内部是通过AQS的state变量来控制的
内部也分为公平锁和非公平锁,两者的区别和reentrantLocK的公平锁、非公平锁一样,在抢占资源的时候,少了一个判断:是否需要排队;非公平锁在抢占资源的时候,无需判断是否需要排队,直接尝试cas,抢占成功,就无须排队,否则就去排队

Semaphore semaphore = new Semaphore(3);
这行代码会初始化一个公平锁或者非公平锁,入参指定的变量是锁对应的state,也就是最多可以有多少个线程来加锁;构造函数最终会调用这里,将state设置为3

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

抢占资源:acquire()

这里不指定入参,默认每次抢占一个资源,可以指定一个线程抢占的资源数;对于底层来讲,没有区别,无非就是在尝试加锁的时候,将state-指定的数量即可,如果不指定,默认是-1

public void acquire() throws InterruptedException {
    
    
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    
    
    if (Thread.interrupted())
        throw new InterruptedException();
    // 这里返回值小于0,表示需要去排队
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

protected int tryAcquireShared(int acquires) {
    
    
    for (;;) {
    
    
        /**
        * 这里是尝试判断是否需要进行排队,如果返回false,表示可以抢占资源
        */
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        /**
                 * 这里在将state减去指定的数量之后,如果remaining小于0,就表示资源不够本次线程来加锁,就会去排队
                 * 如果remaining还大于等于0,就表示当前剩余的资源,可以满足本次线程的加锁,就进行cas
                 */
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

排队

/**
* semaphore在抢占资源失败,会来这里,进行排队
* 1.生成node节点
* 2.判断当前node节点是否是第二个节点(也即第一个排队的节点),如果是,尝试抢占资源,抢占成功,就无须排队,return
* 3.抢占失败,或者不是第一个排队的节点,就将前一个节点的waitStatus设置为-1,然后调用park()方法
* @param arg
* @throws InterruptedException
*/
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    
    
    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);
    }
}

释放资源:semaphore.release();

1、将state变量 + 1;如果这里release()不指定数量,默认是1;也可以指定一次要释放多少个资源
2、通过cas将state变量更新成功之后,尝试唤醒排队队列中的线程
这里的这个方法是aqs中的,都是来释放资源或者解锁的
tryReleaseShared:是尝试释放资源或者解锁,由于每个工具类解锁或者释放资源逻辑不同,所以这个方法放到子类中进行实现
doReleaseShared:这是尝试唤醒队列中排队的线程,这个方法是共用的,就可以直接在父类中实现
这就是模板设计模式,aqs中大量用到了该设计模式

扫描二维码关注公众号,回复: 12613165 查看本文章
public final boolean releaseShared(int arg) {
    
    
    // 尝试释放资源,释放资源成功,就唤醒排队线程
    if (tryReleaseShared(arg)) {
    
    
        doReleaseShared();
        return true;
    }
    return false;
}

/**
* semaphore尝试释放占用的资源,将state + release
* @param releases
* @return
*/
protected final boolean tryReleaseShared(int releases) {
    
    
    for (;;) {
    
    
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

doReleaseShared()方法,在前面countDownLatch中有调用,我就不贴源码了

所以:semaphore的流程是这样的:
1.在创建semaphore的时候,指定同一时刻,允许的最大信号量
2.多个线程会尝试占有资源,占有资源的过程是:将state-1,判断state是否小于0,然后通过cas,将-1后的state写到内存中
3.如果线程抢占资源时,返回的state-1 < 0;表示当前信号量资源已经被占完,需要进行排队
4.在排队的时候,会先创建AQS中排队的node节点,如果当前是第一个排队的线程,就创建一个空节点,然后把刚创建的node节点,添加到空节点的后面;否则,就直接在AQS队列中,添加node节点即可;然后接下来,会区分两种情况
4.1 如果当前排队的是AQS队列中第一个排队的线程,在插入队列之后,会进行一次尝试加锁的过程,因为有可能这个线程在处理排队的过程中,已经有线程释放了资源;
如果抢占资源成功,就把当前线程对应的node节点设置为头结点(thread设置为null;node.prev设置为null);然后唤醒下一个waitStatus为-1的节点(正常情况下,就是下一个节点)
如果抢占失败,就执行4.2的逻辑
4.2 如果抢占失败,或者是非第一个排队线程,就执行以下逻辑:判断上一个节点的waitStatus是否为-1,如果不为-1,就将上一个node节点的waitStatus设置为-1,然后unpark()

5.在释放资源的时候,会获取到当前的state,然后+1,通过cas,写到state变量中;然后就唤醒下一个waitStatus为-1的节点;如果head == tail;表示当前队列中,已经没有排队的线程,就无需唤醒,直接return即可

6.在使用Semapore的时候,创建公平锁和非公平锁的唯一区别是:
在尝试占有资源的时候,非公平锁,会直接cas;而公平锁会先判断是否需要排队,如果需要排队,就返回-1,然后进行排队,不需要排队,就cas自旋

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/109441969