二刷Java多线程:Java并发包中线程同步器详解(CountDownLatch、CyclicBarrier、Semaphore)

一、等待多线程完成的CountDownLatch

1、案例介绍

public class CountDownLatchDemo {
    private static CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("child threadOne over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        });

        executorService.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("child threadTwo over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        });
        System.out.println("wait all child thread over");
        countDownLatch.await();
        System.out.println("all child thread over");
        executorService.shutdown();
    }
}

创建了一个CountDownLatch实例,因为有两个子线程所以构造函数的传参为2。主线程调用countDownLatch.await()方法后会被阻塞。子线程执行完毕后调用countDownLatch.countDown()方法让countDownLatch内部的计数器减1,所有子线程执行完毕并调用countDown()方法后计数器会变为0,这时候主线程的await()方法才会返回

2、CountDownLatch源码分析

在这里插入图片描述
CountDownLatch是使用AQS实现的,通过下面的构造函数把计数器的值赋给了AQS的状态变量state,也就是使用AQS的状态值来表示计数器值

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
        Sync(int count) {
            setState(count);
        }

1)、await()方法

当线程调用CountDownLatch对象的await()方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:当所有线程都调用了CountDownLatch对象的countDown()方法后,也就是计数器的值为0时;其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException异常,然后返回

    public void await() throws InterruptedException {
        //调用AQS中的模板方法acquireSharedInterruptibly
        sync.acquireSharedInterruptibly(1);
    }

AQS中的acquireSharedInterruptibly方法:

    //AQS获取共享资源时可被中断的方法
	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //如果线程被中断则抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //调用CountDownLatch中sync重写的tryAcquireShared方法,查看当前计数器值是否为0,为0则直接返回,否则进入AQS的队列等待
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

sync类实现的AQS的接口:

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

2)、countDown()方法

    public void countDown() {
        //调用AQS中的模板方法releaseShared
        sync.releaseShared(1);
    }

AQS中的releaseShared方法:

    public final boolean releaseShared(int arg) {
        //调用CountDownLatch中sync重写的tryReleaseShared方法
        if (tryReleaseShared(arg)) {
            //AQS的释放资源方法
            doReleaseShared();
            return true;
        }
        return false;
    }

sync类实现的AQS的接口:

        protected boolean tryReleaseShared(int releases) {
            //循环进行CAS,直到当前线程成功完成CAS使计数器值(状态值state)减1并更新到state
            for (;;) {
                int c = getState();
                //如果当前状态值为0则直接返回(为了防止当计数器值为0后,其他线程又调用了countDown方法,防止状态值变为负数)
                if (c == 0)
                    return false;
                //使用CAS让计数器值减1
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

二、同步屏障CyclicBarrier

1、案例介绍

public class CyclicBarrierDemo {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "执行回调方法");
        }
    });

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " step1");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step2");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " step1");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step2");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        executorService.shutdown();
    }
}

运行结果:

pool-1-thread-1 step1
pool-1-thread-2 step1
pool-1-thread-2执行回调方法
pool-1-thread-1 step2
pool-1-thread-2 step2
pool-1-thread-2执行回调方法
pool-1-thread-1 step3
pool-1-thread-2 step3

多个线程之间是相互等待的,假如计数器值为N,那么随后调用await()方法的N-1个线程都会因为到达屏障点而被阻塞,当第N个线程调用await()后,计数器值为0了,这时候第N个线程才会发出通知唤醒前面的N-1个线程。也就是当全部线程都到达屏障点时才能一块继续向下执行

此外从上面的案例中还可以得知,CyclicBarrier的计数器具备自动重置的功能,可以循环利用,回调任务是由最后一个到达屏障的线程执行的

2、CyclicBarrier源码分析

在这里插入图片描述
CyclicBarrier基于独占锁实现,本质底层还是基于AQS的。parties用来记录线程个数,这里表示多少线程调用await()后,所有线程才会冲破屏障继续往下运行。而count—开始等于parties,每当有线程调用await()方法就递减1,当count为0时就表示所有线程都到了屏障点。而parties始终用来记录总的线程个数,当count计数器值变为0后,会将parties的值赋给count,从而进行复用

扫描二维码关注公众号,回复: 8875751 查看本文章
   public CyclicBarrier(int parties, Runnable barrierAction) {
       if (parties <= 0) throw new IllegalArgumentException();
       this.parties = parties;
       this.count = parties;
       this.barrierCommand = barrierAction;
   }

还有一个变量barrierCommand也通过构造函数传递,这是一个任务,这个任务的执行时机是当所有线程都到达屏障点后。使用lock首先保证了更新计数器count的原子性,另外使用lock的条件变量trip支持线程间使用await()和signal()操作进行同步

在变量generation内部有一个变量broken,其用来记录当前屏障是否被打破。这里的broken并没有被声明为volatile的,因为是在锁内使用变量,所以不需要声明

    private static class Generation {
        boolean broken = false;
    }

await()方法:

当前线程调用CyclicBarrier的该方法时会被阻塞,直到满足下面条件之一才会返回:parties个线程都调用了await()方法,也就是线程都到了屏障点;其他线程调用了当前线程的interrupt()方法中断了当前线程,则当前线程会抛出InterruptedException异常而返回;与当前屏障点关联的Generation对象的broken标志被设置为true时,会抛出BrokenBarrierException异常,然后返回

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            //调用内部的dowait()方法,第一个参数为flase表示不设置超时时间
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

dowait()方法:

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

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

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            //如果index==0则说明所有线程都到了屏障点,此时执行初始化时传递的任务
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //(1)执行任务
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //(2)激活其他因调用await方法而被阻塞的线程,并重置CyclicBarrier
                    nextGeneration();
                    //返回
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            //(3)index!=0
            for (;;) {
                try {
                    //没有设置超时时间
                    if (!timed)
                        trip.await();
                    //设置了超时时间
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt();
                    }
                }

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

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

nextGeneration()方法:

    private void nextGeneration() {
        //唤醒条件队列里面阻塞的线程
        trip.signalAll();
        //重置CyclicBarrier
        count = parties;
        generation = new Generation();
    }

当一个线程调用了dowait方法后,首先会获取独占锁lock,如果创建CycleBarrier时传递的参数为10,那么后面9个调用线程会被阻塞。然后当前获取到锁的线程会对计数器count进行递减操作,递减后count=index=9,因为index!=0所以当前线程会执行代码(3)。如果当前线程调用的是无参数的await()方法,则这里timed=false,所以当前线程会被放入条件变量trip的条件阻塞队列,当前线程会被挂起并释放获取的lock锁。如果调用的是有参数的await方法则timed=true,然后当前线程也会被放入条件变量的条件队列并释放锁资源,不同的是当前线程会在指定时间超时后自动被激活

当第一个获取锁的线程由于被阻塞释放锁后,被阻塞的9个线程中有一个会竞争到lock锁,然后执行与第一个线程同样的操作,直到最后一个线程获取到lock锁,此时已经有9个线程被放入了条件变量trip的条件队列里面。最后count=index等于0,所以执行代码(1),如果创建CyclicBarrier时传递了任务,则在其他线程被唤醒前先执行任务,任务执行完毕后再执行代码(2),唤醒其他9个线程,并重置CyclicBarrier,然后这10个线程就可以继续向下运行了

三、使用CountDownLatchDown和CyclicBarrier优化对账系统

在学习极客时间的《Java并发编程实战》这门课时,关于CountDownLatchDown和CyclicBarrier的文章《CountDownLatch和CyclicBarrier:如何让多线程步调一致?》中,王宝令老师通过一个优化对账系统的案例更好的诠释了CountDownLatchDown和CyclicBarrier的使用,同时作者解决问题的思路也值得我们借鉴,下面是我对文章进行的总结和梳理,同时也强烈推荐学习一下这门课程,对并发编程的讲解还是很不错的

对账系统业务:用户通过在线商城下单,会生成电子订单,保存在订单库;之后物流会生成派送单给用户发货,派送单保存在派送单库。为了防止漏派送或者重复派送,对账系统每天还会校验是否存在异常订单
在这里插入图片描述
对账系统的核心代码如下,就是在一个单线程里面循环查询订单、派送单,然后执行对账,最后将写入差异库

		while (存在未对账订单) {
			//查询未对账订单
			pos = getPOrders();
			//查询派送单
			dos = getDOrders();
			//执行对账操作
			diff = check(pos, dos);
			//差异写入差异库
			save(diff);
		}

首先要优化性能,就要找到这个对账系统的瓶颈所在:目前的对账系统,由于订单量和派送单量巨大,所以查询未对账订单getPOrders()和查询派送单getDOrders()相对较慢,目前对账系统是单线程执行的,图形化后是下图这个样子
在这里插入图片描述
查询未对账订单getPOrders()和查询派送单getDOrders()这两个操作并没有先后顺序的依赖可以并行处理,执行过程如下图所示。对比一下单线程的执行示意图,在同等时间内,并行执行的吞吐量近乎单线程的2倍
在这里插入图片描述

1、用CountDownLatch实现线程等待

		//创建2个线程的线程池
		Executor executor = Executors.newFixedThreadPool(2);
		while (存在未对账订单) {
			//计数器初始化为2
			CountDownLatch latch = new CountDownLatch(2);
			//查询未对账订单
			executor.execute(() -> {
				pos = getPOrders();
				latch.countDown();
			});
			//查询派送单
			executor.execute(() -> {
				dos = getDOrders();
				latch.countDown();
			});

			//等待两个查询操作结束
			latch.await();

			//执行对账操作
			diff = check(pos, dos);
			//差异写入差异库
			save(diff);
		}

在while循环里面,创建了一个CountDownLatch,计数器的初始值等于2,之后在pos = getPOrders();和dos = getDOrders();两条语句的后面对计数器执行减1操作,这个对计数器减1的操作是通过调用latch.countDown();来实现的。在主线程中,通过调用latch.await()来实现对计数器等于0的等待

2、进一步优化性能

getPOrders()和getDOrders()这两个查询操作和对账操作check()、save()之间也是可以并行的,也就是说,在执行对账操作的时候,可以同时去执行下一轮的查询操作,如下图所示
在这里插入图片描述
针对对账这个项目,设计了两个队列,并且两个队列的元素之间还有对应关系。具体如下图所示,订单查询操作将订单查询结果插入订单队列,派送单查询操作将派送单插入派送单队列,这两个队列的元素之间是有一一对应的关系的。两个队列的好处是,对账操作可以每次从订单队列出一个元素,从派送单队列出一个元素,然后对这两个元素执行对账操作,这样数据一定不会乱掉
在这里插入图片描述
线程T1和线程T2只有都生产完1条数据的时候,才能一起向下执行,也就是说,线程T1和线程T2要互相等待,步调要一致;同时当线程T1和T2都生产完一条数据的时候,还要能够通知线程T3执行对账操作
在这里插入图片描述

3、用CyclicBarrier实现线程同步

	//订单队列
	Vector<P> pos;
	//派送单队列
	Vector<D> dos;
	//执行回调的线程池
	Executor executor = Executors.newFixedThreadPool(1);
	final CyclicBarrier barrier = new CyclicBarrier(2, () -> {
		executor.execute(() -> check());
	});

	void check() {
		P p = pos.remove(0);
		D d = dos.remove(0);
		//执行对账操作
		diff = check(p, d);
		//差异写入差异库
		save(diff);
	}

	void checkAll() {
		//循环查询订单库
		Thread T1 = new Thread(() -> {
			while (存在未对账订单) {
				//查询订单库
				pos.add(getPOrders());
				//等待
				barrier.await();
			}
		});
		T1.start();
		//循环查询运单库
		Thread T2 = new Thread(() -> {
			while (存在未对账订单) {
				//查询运单库
				dos.add(getDOrders());
				//等待
				barrier.await();
			}
		});
		T2.start();
	}

首先创建了一个计数器初始值为2的CyclicBarrier,还传入了一个回调函数,当计数器减到0的时候,会调用这个回调函数

线程T1负责查询订单,当查出一条时,调用barrier.await()来将计数器减1,同时等待计数器变为0;线程T2负责查询派送单,当查出一条时,也调用barrier.await()来将计数器减1,同时等待计数器变为0;当T1和T2都调用barrier.await()的时候,计数器会减到0,此时T1和T2就可以执行下一条语句了,同时会调用barrier的回调函数来执行对账操作

CyclicBarrier的计数器有自动重置的功能,当减到0的时候,会自动重置设置的初始值

回调函数中使用了一个固定大小为1的线程池。首先使用线程池是为了异步操作,否则回调函数是同步调用的,也就是本次对账操作执行完才能进行下一轮的检查;其次线程数量固定为1,防止了多线程并发导致的数据不一致,因为订单和派送单是两个队列,只有单线程去两个队列中取消息才不会出现消息不匹配的问题

4、总结

CountDownLatch主要用来解决一个线程等待多个线程的场景,而CyclicBarrier是一组线程之间互相等待,而且CyclicBarrier的计数器具备自动重置的功能,可以循环利用,CyclicBarrier还可以设置回调函数

四、信号量Semaphore

1、信号量模型

信号量模型包括一个计数器,一个等待队列,三个方法。在信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down()和up()
在这里插入图片描述

  • init():设置计数器的初始值
  • down():计数器的值减1;如果此时计数器的值小于0,则当前线程将被阻塞,否则当前线程可以继续执行
  • up():计数器的值加1;如果此时计数器的值小于或者等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除

init()、down()和up()三个方法都是原子性的。在Java SDK里面,信号量模型是由java.util.concurrent.Semaphore实现的,Semaphore这个类能够保证这三个方法都是原子操作

信号量模型里面down()、up()这两个操作历史上最早称为P操作和V操作,所以心好累模型也被称为PV原语。在Semaphore中,down()和up()对应的则是acquire()和release()

2、Semaphore使用

1)、使用Semaphore实现累加器(互斥)

    static int count;
    //初始化信号量
    static final Semaphore semaphore = new Semaphore(1);

    //用信号量保证互斥    
    static void addOne() {
        try {
            semaphore.acquire();
            count += 1;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }

假设两个线程T1和T2同时访问addOne()方法,当它们同时调用acquire()的时候,由于acquire()是一个原子操作,所以只能由一个线程(假设T1)把信号量里的计数器减为0,另外一个线程(T2)则是将计数器减为-1。对于线程T1,信号量里面的计数器的值是0,大于等于0,所以线程T1会继续执行;对于线程T2,信号量里面的计数器的值是-1,小于0,按照信号量模型里的对down()操作的描述,线程T2将被阻塞。所以此时只有线程T1会进入临界区执行count += 1

当线程T1执行release()操作,也就是up()操作的时候,信号量里计数器的值是-1,加1之后的值是0,小于等于0,按照信号量模型里对up()操作的描述,此时等待队列中的T2将会被唤醒。于是T2在T1执行完临界区代码之后才获得了进入临界区执行的机会,从而保证了互斥性

2)、使用Semaphore控制同时访问特定资源的线程数量

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(8);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}

运行结果:第一次有8个线程执行了打印,等待5秒后,后两个线程执行了打印

3)、使用Semaphore实现一个对象池

public class ObjPool<T, R> {
	final List<T> pool;
	//用信号量实现限流器
	final Semaphore sem;

	//构造函数
	ObjPool(int size, T t) {
		pool = new Vector<T>();
		for (int i = 0; i < size; i++) {
			pool.add(t);
		}
		sem = new Semaphore(size);
	}

	//利用对象池的对象,调用func
	R exec(Function<T, R> func) throws InterruptedException {
		T t = null;
		sem.acquire();
		try {
			t = pool.remove(0);
			return func.apply(t);
		} finally {
			pool.add(t);
			sem.release();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		//创建对象池
		ObjPool<Long, String> pool = new ObjPool<Long, String>(10, 2L);
		//通过对象池获取t,之后执行
		pool.exec(t -> {
			System.out.println(t);
			return t.toString();
		});
	}
}

Semaphore可以允许多个线程访问一个临界区,用Vector保存对象实例,Vector是线程安全的,用Semaphore 实现限流器。关键的代码是ObjPool里面的exec()方法,这个方法里面实现了限流的功能。在这个方法里面,我们首先调用acquire()方法(与之匹配的是在finally里面调用release()方法),假设对象池的大小是10,信号量的计数器初始化为10,那么前10个线程调用acquire()方法,都能继续执行,而其他线程则会阻塞在acquire()方法上。对于通过信号灯的线程,我们为每个线程分配了一个对象t(这个分配工作是通过pool.remove(0)实现的),分配完之后会执行一个回调函数func,而函数的参数正是前面分配的对象t;执行完回调函数之后,它们就会释放对象(这个释放工作是通过pool.add(t)实现的),同时调用release()方法来更新信号量的计数器。如果此时信号量里计数器的值小于等于0,那么说明有线程在等待,此时会自动唤醒等待的线程

3、Semaphore源码分析

在这里插入图片描述
Semaphore还是使用AQS实现的。Sync只是对AQS的一个修饰,并且Sync有两个实现类,用来指定获取信号量时是否采用公平策略

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

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

Semaphore默认釆用非公平策略,如果需要使用公平策略则可以使用带两个参数的构造函数来构造Semaphore对象

如CountDownLatch构造函数传递 的初始化信号量个数permits被赋给了AQS的state状态变量一样,这里AQS的state值表示当前持有的信号量个数

1)、acquire()方法

    public void acquire() throws InterruptedException {
        //调用AQS中的模板方法acquireSharedInterruptibly
        sync.acquireSharedInterruptibly(1);
    }

AQS中的acquireSharedInterruptibly方法:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //调用Semaphore中sync重写的tryAcquireShared方法,根据构造函数确定使用的公平策略
        if (tryAcquireShared(arg) < 0)
            //如果获取失败则放入阻塞队列。然后再次尝试,如果失败则调用park方法挂起当前线程
            doAcquireSharedInterruptibly(arg);
    }

非公平策略:

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                //获取当前信号量值
                int available = getState();
                //计算当前剩余值
                int remaining = available - acquires;
                //如果当前剩余值小于0或者CAS设置成功则返回
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

如果线程A先调用了aquire()方法获取信号量,但是当前信号量个数为0,那么线程A会被放入AQS的阻塞队列。过一段时间后线程C调用了release()方法释放了一个信号量,如果当前没有其他线程获取信号量,那么线程A就会被激活,然后获取该信号量,但是假如线程C释放信号量后,线程C调用了aquire()方法,那么线程C就会和线程A去竞争这个信号量资源。如果采用非公平策略,线程C完全可以在线程A被激活前,或者激活后先于线程A获取到该信号量,也就是在这种模式下阻塞线程和当前请求的线程是竞争关系,而不遵循先来先得的策略

公平策略:

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                //公平策略是看当前线程节点的前驱节点是否也在等待获取该资源,如果是则自己放弃获取的权限, 然后当前线程会被放入AQS阻塞队列,否则就去获取
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

2)、release()方法

    public void release() {
        //调用AQS中的模板方法releaseShared
        sync.releaseShared(1);
    }

AQS中的releaseShared方法:

    public final boolean releaseShared(int arg) {
        //尝试释放资源
        if (tryReleaseShared(arg)) {
            //释放资源成功则调用park方法唤醒AQS队列里面最先挂起的线程
            doReleaseShared();
            return true;
        }
        return false;
    }

Sync中重写的tryReleaseShared方法:

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                //获取当前信号量值
                int current = getState();
                //将当前信号量值增加releases
                int next = current + releases;
                if (next < current) 
                    throw new Error("Maximum permit count exceeded");
                //使用CAS保证更新信号量值的原子性
                if (compareAndSetState(current, next))
                    return true;
            }
        }
发布了177 篇原创文章 · 获赞 407 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/102242305