Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore

CountDownLatch的用法

CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

public CountDownLatch(int count) {  };  //参数count为计数值

然后下面这3个方法是CountDownLatch类中最重要的方法:

public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };  //将count值减1

调用await()

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CountDownLatchExample1 {

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
        Thread.sleep(100);
    }
}

结果:
finish最后输出
调用await(timeout,timeunit)

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CountDownLatchExample2 {

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await(10, TimeUnit.MILLISECONDS);
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
    }
}

结果:
finish刚开始就输出了

CyclicBarrier用法

CyclicBarrier可以被重用。当调用await()方法之后,线程就处于barrier了。等待指定数量的线程都准备完成执行后续操作。
CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:

  public CyclicBarrier(int parties, Runnable barrierAction) {
  		//Runnable中可以执行额外的操作
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:

 public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
 public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

await()当准备的线程都进入barrier状态后才会执行后续操作
await(timeout,timeunit)已进入barrier状态的线程在等待指定时间后,就会执行后续操作,
await()使用:等待给出的线程个数准备完毕,在继续执行后续操作代码如下:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CyclicBarrierExample1 {

    private static CyclicBarrier barrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {

        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }

    private static void race(int threadNum) throws Exception {
        Thread.sleep(1000);
        log.info("{} is ready", threadNum);
        barrier.await();
        log.info("{} continue", threadNum);
    }
}

执行结果

15:10:46.491 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 0 is ready
15:10:47.490 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 1 is ready
15:10:48.490 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 2 is ready
15:10:49.490 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 3 is ready
15:10:50.490 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 4 is ready
15:10:50.490 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 0 continue
15:10:50.490 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 1 continue
15:10:50.490 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 2 continue
15:10:50.490 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 3 continue
15:10:50.490 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 4 continue
15:10:51.490 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 5 is ready
15:10:52.490 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 6 is ready
15:10:53.490 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 7 is ready
15:10:54.490 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 8 is ready
15:10:55.490 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 9 is ready
15:10:55.490 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 9 continue
15:10:55.490 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 5 continue
15:10:55.490 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 6 continue
15:10:55.490 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 7 continue
15:10:55.490 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample1 - 8 continue

Process finished with exit code 0

await(timeout,timeunit)使用:等待给出的线程个数准备完毕,在继续执行后续操作代码如下:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CyclicBarrierExample2 {

    private static CyclicBarrier barrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {

        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }

    private static void race(int threadNum) throws Exception {
        Thread.sleep(1000);
        log.info("{} is ready", threadNum);
        try {
            barrier.await(2000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.warn("BarrierException", e);
        }
        log.info("{} continue", threadNum);
    }
}

执行结构:

15:15:47.581 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 0 is ready
15:15:48.580 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 1 is ready
15:15:49.580 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 2 is ready
15:15:49.587 [pool-1-thread-3] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:49.587 [pool-1-thread-1] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.TimeoutException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:49.588 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 0 continue
15:15:49.587 [pool-1-thread-2] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:49.588 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 2 continue
15:15:49.588 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 1 continue
15:15:50.580 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 3 is ready
15:15:50.580 [pool-1-thread-4] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:50.580 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 3 continue
15:15:51.580 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 4 is ready
15:15:51.580 [pool-1-thread-2] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:51.580 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 4 continue
15:15:52.580 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 5 is ready
15:15:52.580 [pool-1-thread-4] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:52.580 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 5 continue
15:15:53.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 6 is ready
15:15:53.583 [pool-1-thread-2] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:53.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 6 continue
15:15:54.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 7 is ready
15:15:54.583 [pool-1-thread-2] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:54.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 7 continue
15:15:55.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 8 is ready
15:15:55.583 [pool-1-thread-2] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:55.583 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 8 continue
15:15:56.584 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 9 is ready
15:15:56.584 [pool-1-thread-4] WARN com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - BarrierException
java.util.concurrent.BrokenBarrierException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.race(CyclicBarrierExample2.java:37)
	at com.javaedge.concurrency.example.aqs.CyclicBarrierExample2.lambda$main$0(CyclicBarrierExample2.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
15:15:56.584 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample2 - 9 continue

Process finished with exit code 0

从结果可以看出线程123进入barrier状态后,线程3等待超时并抛出异常BrokenBarrierException后,继续执行后续

使用CyclicBarrier(N,Runnable)可以执行额外的操作,代码实例

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CyclicBarrierExample3 {

    private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
        log.info("callback is running");
    });
	//() -> {log.info("callback is running")},jdk1.8的新特性Lambda表达式,相当于实现了Runnable接口并重写了run()方法
    public static void main(String[] args) throws Exception {

        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }

    private static void race(int threadNum) throws Exception {
        Thread.sleep(1000);
        log.info("{} is ready", threadNum);
        barrier.await();
        log.info("{} continue", threadNum);
    }
}

执行结果

15:29:01.159 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 0 is ready
15:29:02.158 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 1 is ready
15:29:03.159 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 2 is ready
15:29:04.159 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 3 is ready
15:29:05.159 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 4 is ready
15:29:05.159 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - callback is running
15:29:05.159 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 4 continue
15:29:05.159 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 0 continue
15:29:05.159 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 1 continue
15:29:05.159 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 2 continue
15:29:05.159 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 3 continue
15:29:06.159 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 5 is ready
15:29:07.159 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 6 is ready
15:29:08.159 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 7 is ready
15:29:09.159 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 8 is ready
15:29:10.159 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 9 is ready
15:29:10.159 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - callback is running
15:29:10.159 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 9 continue
15:29:10.159 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 7 continue
15:29:10.159 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 5 continue
15:29:10.159 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 6 continue
15:29:10.159 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.CyclicBarrierExample3 - 8 continue

Process finished with exit code 0

从控制台日志中可以看出,当指定线程完成所有操作后,回调了Runnable接口的实现类中的run()方法

Semaphore的用法

Semaphore 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

    public Semaphore(int permits) {
    	//参数permits表示许可数量,即同时可以有多少个线程
        sync = new NonfairSync(permits);
    }
     public Semaphore(int permits, boolean fair) {
     	//fair表示是否公平的,等待时间越久的越容易获取许可
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }   

其中获取许可和释放许可的方法如下:

public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { }    //获取permits个许可
public void release() { }          //释放一个许可
public void release(int permits) { }    //释放permits个许可

acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

public boolean tryAcquire() { };    //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false

另外还可以通过availablePermits()方法得到可用的许可数目。
acquire()获取一个许可,和release()释放一个许可,代码实例:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class SemaphoreExample1 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire(); // 获取一个许可
                    test(threadNum);
                    semaphore.release(); // 释放一个许可
                    log.info("{}", threadNum+"《释放许可");
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum+"》获取许可");
        Thread.sleep(1000);
    }
}

执行结果

16:00:29.474 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 1》获取许可
16:00:29.474 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 2》获取许可
16:00:29.474 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 0》获取许可
16:00:30.478 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 2《释放许可
16:00:30.478 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 0《释放许可
16:00:30.478 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 3》获取许可
16:00:30.478 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 1《释放许可
16:00:30.478 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 4》获取许可
16:00:30.478 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 5》获取许可
16:00:31.479 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 4《释放许可
16:00:31.479 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 6》获取许可
16:00:31.479 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 7》获取许可
16:00:31.479 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 8》获取许可
16:00:31.479 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 3《释放许可
16:00:31.479 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 5《释放许可
16:00:32.479 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 7《释放许可
16:00:32.479 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 8《释放许可
16:00:32.479 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 6《释放许可
16:00:32.479 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 9》获取许可
16:00:32.479 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 10》获取许可
16:00:32.479 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 11》获取许可
16:00:33.479 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 9《释放许可
16:00:33.479 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 11《释放许可
16:00:33.479 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 12》获取许可
16:00:33.479 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 13》获取许可
16:00:33.479 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 10《释放许可
16:00:33.479 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 14》获取许可
16:00:34.479 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 14《释放许可
16:00:34.479 [pool-1-thread-16] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 15》获取许可
16:00:34.479 [pool-1-thread-17] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 16》获取许可
16:00:34.479 [pool-1-thread-18] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 17》获取许可
16:00:34.479 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 12《释放许可
16:00:34.479 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 13《释放许可
16:00:35.479 [pool-1-thread-16] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 15《释放许可
16:00:35.479 [pool-1-thread-20] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 19》获取许可
16:00:35.479 [pool-1-thread-19] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 18》获取许可
16:00:35.479 [pool-1-thread-18] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 17《释放许可
16:00:35.479 [pool-1-thread-17] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 16《释放许可
16:00:36.479 [pool-1-thread-19] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 18《释放许可
16:00:36.479 [pool-1-thread-20] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample1 - 19《释放许可

同时只能有3个获取许可
acquire(permits)获取permits个许可,和release(permits)释放permits个许可,代码实例:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class SemaphoreExample2 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire(3); // 获取多个许可
                    test(threadNum);
                    semaphore.release(3); // 释放多个许可
                    log.info("{}", threadNum+"》释放许可");
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum+"》获取许可");
        Thread.sleep(1000);
    }
}

执行结果

16:01:43.299 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 0》获取许可
16:01:44.303 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 0》释放许可
16:01:44.303 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 1》获取许可
16:01:45.303 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 1》释放许可
16:01:45.303 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 2》获取许可
16:01:46.303 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 3》获取许可
16:01:46.303 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 2》释放许可
16:01:47.303 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 3》释放许可
16:01:47.303 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 4》获取许可
16:01:48.303 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 4》释放许可
16:01:48.303 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 5》获取许可
16:01:49.303 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 5》释放许可
16:01:49.303 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 6》获取许可
16:01:50.303 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 6》释放许可
16:01:50.303 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 7》获取许可
16:01:51.303 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 7》释放许可
16:01:51.303 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 8》获取许可
16:01:52.303 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 8》释放许可
16:01:52.303 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 9》获取许可
16:01:53.303 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 9》释放许可
16:01:53.303 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 10》获取许可
16:01:54.303 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 10》释放许可
16:01:54.303 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 11》获取许可
16:01:55.303 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 11》释放许可
16:01:55.303 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 12》获取许可
16:01:56.303 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 12》释放许可
16:01:56.303 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 13》获取许可
16:01:57.303 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 13》释放许可
16:01:57.303 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 14》获取许可
16:01:58.304 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 14》释放许可
16:01:58.304 [pool-1-thread-16] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 15》获取许可
16:01:59.304 [pool-1-thread-16] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 15》释放许可
16:01:59.304 [pool-1-thread-17] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 16》获取许可
16:02:00.304 [pool-1-thread-17] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 16》释放许可
16:02:00.304 [pool-1-thread-18] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 17》获取许可
16:02:01.304 [pool-1-thread-18] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 17》释放许可
16:02:01.304 [pool-1-thread-19] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 18》获取许可
16:02:02.304 [pool-1-thread-19] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 18》释放许可
16:02:02.304 [pool-1-thread-20] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 19》获取许可
16:02:03.304 [pool-1-thread-20] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample2 - 19》释放许可

Process finished with exit code 0

tryAcquire()尝试获取一个许可代码实例:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SemaphoreExample4 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    if (semaphore.tryAcquire()) { // 尝试获取一个许可
                        test(threadNum);
                        semaphore.release(); // 释放一个许可
                        log.info("{}",threadNum + "释放许可");
                    }
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum+"获取许可");
        Thread.sleep(1000);
    }
}

执行结果:

16:08:27.863 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 3>获取许可
16:08:27.863 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 0>获取许可
16:08:27.863 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 1>获取许可
16:08:28.867 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 3>释放许可
16:08:28.867 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 1>释放许可
16:08:28.867 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample3 - 0>释放许可
Process finished with exit code 0

同一时间只有三个线程获取许可成功,其他获取许可失败返回false而不执行
tryAcquire(5000, TimeUnit.MILLISECONDS)在指定时间尝试获取一个许可代码实例:

package com.javaedge.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SemaphoreExample4 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) { // 尝试获取一个许可
                        test(threadNum);
                        semaphore.release(); // 释放一个许可
                        log.info("{}",threadNum + "释放许可");
                    }
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum+"获取许可");
        Thread.sleep(1000);
    }
}

执行结果:

16:10:09.478 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 0获取许可
16:10:09.478 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 1获取许可
16:10:09.478 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 2获取许可
16:10:10.482 [pool-1-thread-3] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 2释放许可
16:10:10.482 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 3获取许可
16:10:10.482 [pool-1-thread-1] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 0释放许可
16:10:10.482 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 4获取许可
16:10:10.482 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 5获取许可
16:10:10.482 [pool-1-thread-2] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 1释放许可
16:10:11.482 [pool-1-thread-6] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 5释放许可
16:10:11.482 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 6获取许可
16:10:11.482 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 7获取许可
16:10:11.482 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 8获取许可
16:10:11.482 [pool-1-thread-5] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 4释放许可
16:10:11.482 [pool-1-thread-4] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 3释放许可
16:10:12.482 [pool-1-thread-8] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 7释放许可
16:10:12.482 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 9获取许可
16:10:12.482 [pool-1-thread-9] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 8释放许可
16:10:12.482 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 10获取许可
16:10:12.482 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 11获取许可
16:10:12.482 [pool-1-thread-7] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 6释放许可
16:10:13.482 [pool-1-thread-12] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 11释放许可
16:10:13.482 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 13获取许可
16:10:13.482 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 14获取许可
16:10:13.482 [pool-1-thread-10] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 9释放许可
16:10:13.482 [pool-1-thread-11] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 10释放许可
16:10:13.482 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 12获取许可
16:10:14.482 [pool-1-thread-13] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 12释放许可
16:10:14.482 [pool-1-thread-15] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 14释放许可
16:10:14.482 [pool-1-thread-14] INFO com.javaedge.concurrency.example.aqs.SemaphoreExample4 - 13释放许可
Process finished with exit code 0

在指定时间只有15个线程在指定时间尝试获取许可成功
下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值