java并发编程之CountDownLatch,CyclicBarrier和Semaphore

一、CountDownLatch

CountDownLatch能够让一个线程在等待其他线程全部完成各自任务后再执行。而CountDownLatch是通过计数器来实现的,计数器的初始值即为任务的总数。
举个例子,如,同学聚会结束回家,每个人都要回各自的家,此时计数器的初始值为参加聚会的总人数,而每个人都是一个线程,每个同学到家后,都需要调用countDown方法,对计数器减一,表示完成回家的任务,当所有同学都到家后,主线程才可以执行通知班长全部到家的任务。再比如,所编写的应用程序,希望等待启动框架的线程启动完毕后再执行。
java并发编程之CountDownLatch,CyclicBarrier和Semaphore_第1张图片
可以看到内部只用了一个同步器Sync,继承自AQS,没有公平和非公平实现

1.1 构造方法
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

CountDownLatch的构造方法需要一个count参数,代表初始任务的数量,往后每当调用一次(CountDownLatch.countDown()方法,count都减一,当count减到0的时候,调用CountDownLatch.await()方法的线程就可以执行其任务。

1.2 Sync内部类实现
 private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

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

        int getCount() {
            return getState();
        }
		//尝试获取共享锁
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
		//尝试释放共享锁
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
            	//获取当前状态
                int c = getState();
                //如果为0,则释放失败
                if (c == 0)
                    return false;
                    //释放一次
                int nextc = c-1;
                //CAS更新状态
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

除此之外,CountDownLatch还有以下几个方法:

1.3 await()

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    //AQS中的方法
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)//如果尝试获取锁失败
            doAcquireSharedInterruptibly(arg);//加入AQS队列排队
    }    

tryAcquireShared就是Sync中的tryAcquireShared,当且仅当同步状态为0时返回1,即获取锁成功,因此只要state大于0,即计数没有结束,调用await()的线程都要阻塞排队等待唤醒。

1.4 countDown()
 public void countDown() {
        sync.releaseShared(1);
    }
   public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//尝试释放锁
            doReleaseShared();//释放锁成功就唤醒下一个AQS中排队的线程
            return true;
        }
        return false;
    }  

tryReleaseShared就是上Sync中的方法。每次减一,减到0时返回true,否则返回false

1.await(long timeout,TimeUnit unit)

 public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

此方法在await()的基础上添加了时间限制,如果调用await的线程在到达该时间后,count仍然没有0,其会继续执行,不再等到count到0。

2. public long getCount()

 public long getCount() {
        return sync.getCount();
    }

即获取该CountDownLatch当前的count值

二、Demo应用

package com.wml.test1.countdownlatch;

import java.util.concurrent.CountDownLatch;

/**
 * @author Wang
 * @date 2020/1/2317:27
 */
public class CountDownLatchTest {

	//1.
    private static CountDownLatch finishEat=new CountDownLatch(1);
    //2.
    private static CountDownLatch friends=new CountDownLatch(10);
    public static void main(String[] args) throws InterruptedException {

		//3.
        for (int i = 0; i <10; i++) {
            new Thread(()->{
                try {
                	//4.
                    finishEat.await();
                    System.out.println(Thread.currentThread().getName()+"正在回家");
                    //5.
                    friends.countDown();

	    		System.out.println(Thread.currentThread().getName()+"到家啦");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            },"同学"+(i+1)).start();
        }
        System.out.println("聚会结束,同学们准备回家");
        //6.
        finishEat.countDown();
        //7.
        friends.await();
        //8.
        System.out.println("同学全部到家");
    }
}

说明:
1.代码1创建一个结束聚餐的CountDownLatch,finishEat。初始值为1,代表正在聚餐,当主线程调用countDown()时(代码6),count值从1变为0,代表结束聚餐。这时,其他调用finishEat.await();的线程(各个同学回家的线程)由阻塞状态变为执行。
2.代码2创建一个同学回家的CountDownLatch,friends。初始值为10,代表参加聚会的10个同学,即10个同学需要执行回家任务。回家的任务需要在代码4finishEat.await();后执行。
3.代码3创建10个回家的线程,每个线程中都执行一次friends.countDown();,让friends的count减一
4.代码7friends.await();主线程阻塞等待friends的count变为0时开始执行下面的代码。
结果:

聚会结束,同学们准备回家
同学1正在回家
同学1到家啦
同学2正在回家
同学2到家啦
同学4正在回家
同学4到家啦
同学3正在回家
同学3到家啦
同学5正在回家
同学5到家啦
同学6正在回家
同学6到家啦
同学全部到家

1. CountDownLatch可以看做一个计数器,数量为任务总数,一个或多个线程需要等待所有任务执行完毕后,才可以执行。
2. 内部实现基于AQS的共享模式的锁,像读写锁中的读锁一样,最后介绍的SemphoreTest 也是基于共享锁。因为其需要在获得锁后将所有等待线程都唤醒,而共享锁就可以在获得锁后向同步队列后传播唤醒等待中的线程,但互斥锁就不会传播唤醒。
3. await()方法要等待同步状态为0时才会被唤醒,即所有任务都执行完毕
4. countDown方法每次让同步状态减少1,减到0时表示释放完毕,即所有任务执行完,此时从await()方法返回,继续执行后面的任务。

三、CyclicBarrier

Cyclic(循环的)Barrier(屏障),该工具做的事情是,当一个线程到达一个屏障(同步点/临界点)时会被阻塞,等待到最后一个线程到达该点后,被拦截阻塞的线程才可以继续执行。
比如:同学聚餐,不会是到一个就吃一个,而是到的人先等待,直到所有人都达到饭桌后,才开始吃饭。这里餐桌就类似barrier,每个同学都是一个线程,每个人到饭桌后都被阻塞,直到最后一个同学到达。
而因为CyclicBarrier是可循环的,当一组线程到达后,其仍然有效,可以继续下一组循环。

3.1构造方法

1.默认构造方法

public CyclicBarrier(int parties) {
        this(parties, null);
    }

传入的参数表示需要拦截的线程总数,每当一个线程调用CyclicBarrier.await()方法后,会通知CyclicBarrier该线程已到达屏障。
另外一个构造函数多了一个action参数
2.带任务barrierAction的构造方法
该构造会在所有任务到达屏障后优先执行barrierAction的线程任务

 public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

3.主要属性
ReentrantLock的解读可看java并发编程之ReentrantLock和读写锁ReentrantReadWriteLock

private static class Generation {
        boolean broken = false;
    }

    //使用ReentrantLock重入锁
    private final ReentrantLock lock = new ReentrantLock();
    /**条件队列 */
    private final Condition trip = lock.newCondition();
    /**任务数 */
    private final int parties;
    /* 全部到达屏障后执行的任务 */
    private final Runnable barrierCommand;
    /** 当前代*/
    private Generation generation = new Generation();

    /**
     *当前代在等待的线程数
     */
    private int count;

与CountDownLatch一样,CyclicBarrier也为await提供了超时时间设置。

 public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

线程阻塞直到到达超时时间。

3.2 CyclicBarrier的方法
3.2.1 await
 public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

该方法等待所有的线程都到达指定的临界点。
但如果当前线程不是最后到达的线程,则出于线程调度目的将其禁用,并使其处于休眠状态,直到发生以下情况之一:

  1. 最后一个线程到达
  2. 其他线程中断当前线程
  3. 其他线程中断等待线程之一
  4. 在屏障等待时其他线程超时
  5. 其他线程在此屏障上调用reset方法

await方法中调用的dowait方法
以下是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();
			//代码1
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			//代码2
            int index = --count;
            if (index == 0) {  // index为0了,表示已经是最后一个任务到达屏障
                boolean ranAction = false;
                try {
                	//如果使用了带任务的构造,则先执行该任务
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //调用下代
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 代码3 非最后一个线程,其他线程都会到这
            for (;;) {
                try {
                    if (!timed)
                    	//调用Conditon的await方法,阻塞线程,加入到条件队列
                        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();

                if (g != generation)
                    return index;

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

说明:
代码1:如果当前线程被中断,会调用breakBarrier方法并抛出InterruptedException异常。
breakBarrier方法如下:

private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

1.breakBarrier首先将该代的broken设置为true,代表其被打破。
2.generationCyclicBarrier的一个静态内部类Generation的对象,该对象代表屏障的当前代,可以实现循环等待。broken为true,则该循环结束。
3.同时将count重置为parties
count计数器partiesCyclicBarrier的构造参数,代表拦截的线程数
4.调用tripsignalAll()方法,将所有阻塞的线程唤醒
trip是成员变量Condition的对象,可见是使用Condition实现阻塞队列的。
代码2:
计数器count减一并赋值给int变量index,如果此时index值为0,则判断当前barrierCommand是否为空,如果不为空,则执行该操作的run()方法。
最后置ranActiontrue,个人理解代表继续执行,并执行nextGeneration()进入下一代(下一个循环)。
nextGeneration()代码如下:

 private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

nextGeneration()中将所有线程转移到AQS队列,之后会从await()方法的while循环中退出,继续尝试获取锁 ,重置计数器,并重新new了一个Generation。
而在finally中,如果barrierCommand执行出错,或其他原因,会执行breakBa在这里插入代码片rrier()方法。
代码3:
无限循环直到发生tripped,断开,中断或超时。
timed代表是否开启超时时间,nanos 为设定的超时时间

3.2.2 getNumberWaiting
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }

返回当前有多少个线程阻塞等待在屏障上

3.3 isBroken
boolean isBroken()

返回查询阻塞等待的线程是否被中断打破

四、CyclicBarrier 例子

package com.wml.test1.cyclicbarrier;

import java.util.concurrent.*;

/**
 * @author Wang
 * @date 2020/1/2317:44
 */
public class CyclicBarrierTest {

	//代码1
    private static CyclicBarrier barrier=new CyclicBarrier(6,()->{
        System.out.println("同学们都到齐了,咱们开饭");
    });

    public static void main(String[] args) {

        ExecutorService pool = new ThreadPoolExecutor(6,100,0L,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());

        for (int i = 1; i <= 6; i++) {
            pool.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "到了");
                    //代码2
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + "开吃");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }


    }
}

说明:
该例子为同学聚餐,每到一个同学便阻塞自己,当所有同学都到齐后,输出“开饭”,然后唤醒所有同学,执行“吃饭”

代码1:构造一个CyclicBarrier 屏障,拦截线程数为6,在进入下一代前执行System.out.println("同学们都到齐了,咱们开饭");这一barrierCommand,即当前代结束(所有线程都到达屏障时或其他原因)后优先执行该操作。
代码2:
每个线程到达屏障后执行await方法,直到所有线程都到达,await中会执行唤醒所有阻塞线程的方法,最后才会执行后面的代码。
结果:

pool-1-thread-1到了
pool-1-thread-2到了
pool-1-thread-3到了
pool-1-thread-4到了
pool-1-thread-5到了
pool-1-thread-6到了
同学们都到齐了,咱们开饭
pool-1-thread-6开吃
pool-1-thread-2开吃
pool-1-thread-3开吃
pool-1-thread-5开吃
pool-1-thread-4开吃
pool-1-thread-1开吃

五、CountDownLatch和CyclicBarrier的异同

1.CountDownLatch技术器只能使用一次,CyclicBarrier的可以循环使用(基于代的模式,最后一个线程到达时会调用nextGeneration(),唤醒线程并创建新代,重新初始化count为parties)。
2.CountDownLatch是一个线程等待所有线程完成任务后才会执行自己的任务(显示调用countDown),是阻塞工作线程。CyclicBarrier是所有线程到达某个屏障(临界点)后互相等待,直到所有线程都到达后再共同进行下面的任务,到达后自动唤醒等待线程。
3.CountDownLatch调用countDown方法可以继续执行后面的,只是调用await方法的线程会阻塞等待直到所有线程都调用countDown;CyclicBarrier是所有的线程调用await进行自阻塞,等到所有线程都调用一次await后一起继续执行后面操作。
4.CyclicBarrier可以在所有线程都到达屏障后执行barrierAction操作,完成复杂的逻辑场景。

六、Semaphore

Semaphore即信号量,是用来控制同时访问共享资源的线程数量,或同时执行某个指定操作的数量。比如可以用来实现数据库的连接池、限流等。
如数据库的连接数为20个,这时只能有20个线程同时获取数据量连接,再多的线程只能阻塞等待,当一个线程归还连接后,阻塞的线程才能继续获取。
类结构如下:
java并发编程之CountDownLatch,CyclicBarrier和Semaphore_第2张图片
可见其底层也是使用AQS实现的,同步器支持公平和非公平模式
Semaphore的构造方法:

 public Semaphore(int permits, boolean fair) {
 		//fair为true为公平实现,为false是非公平实现
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    //默认是非公平实现
 public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }    

传入一个整型permits,代表可用的许可证数量。操作时必须先获取许可证,才能继续操作,当操作完成后需释放许可证,其余没有获得许可证的便阻塞等待,直到有线程释放了许可证。
线程使用Semaphore的acquire()方法获取许可证,使用release()方法释放许可证,使用tryAcquire()方法尝试获取许可证.

几个方法都比较简单,一些方法使用了AQS中的,可以先看一下AQS的文章https://blog.csdn.net/weixin_43696529/article/details/104129483

非公平获取许可
 public void acquire() throws InterruptedException {
 		//默认使用可中断方式获取许可,如果尝试获取失败,会进入AQS的队列中排队。
        sync.acquireSharedInterruptibly(1);
    }
//AbstractQueuedSynchronizer#acquireSharedInterruptibly    
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
            //这里的实现在Semaphore的sync中
        if (tryAcquireShared(arg) < 0)//尝试获取许可
            doAcquireSharedInterruptibly(arg);//
    }
  //Semaphore.Sync.tryAcquire()
 public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }

调用同步器sync的nonfairTryAcquireShared

	//非公平模式下获取许可
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
            	//获取当前可获得的许可
                int available = getState();
                //计算减去本次需要的许可后的剩余许可
                int remaining = available - acquires;
                if (remaining < 0 ||//如果许可小于0,则直接返回
                    compareAndSetState(available, remaining))//如果大于0,则CAS更新许可后返回
                    return remaining;
            }
        }

因此返回负数时,表示获取失败;返回正数表示获取成功;
如果获取失败,则调用doAcquireSharedInterruptibly加入AQS自旋获取,具体过程参考AQS一文https://blog.csdn.net/weixin_43696529/article/details/104129483的AQS中共享模式获取锁的介绍

释放许可
 public void release() {
        sync.releaseShared(1);
    }
  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//尝试释放
            doReleaseShared();//如果释放成功,则唤醒下一个节点,具体也参考AQS一文
            return true;
        }
        return false;
    }   

尝试释放许可(共享模式)

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");
                    //CAS更新同步状态
                if (compareAndSetState(current, next))
                    return true;
            }
        }
公平模式获取许可
protected int tryAcquireShared(int acquires) {
            for (;;) {
            	//首先判断有同步队列有没有线程在等待,如果有则返回-1,获取失败
                if (hasQueuedPredecessors())
                    return -1;
                  //如果没有等待,则计算获取许可后的剩余许可,并CAS更新之  
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

其他方法:
acquireUninterruptibly:非中断获取,获取失败也进入AQS队列

 public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

tryAcquire:直接调用非公平模式的获取,不调用acquire,因此获取成功或失败都直接返回,不需要排队

  public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

另外acquire和release都支持获取和释放n个许可。

intavailablePermits() //返回此信号量中当前可用的许可证数。
intgetQueueLength() //返回正在等待获取许可证的线程数。
booleanhasQueuedThreads()//是否有线程正在等待获取许可证。
void reducePermits( int reduction) //减少 reduction 个许可证
Collection getQueuedThreads() //返回所有等待获取许可证的线程集合
semaphore示例:
package com.wml.test1.semphore;

import java.sql.Connection;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.*;

/**
 * @author Wang
 * @date 2020/1/2416:31
 */
public class SemphoreTest {
    private final static int POOL_SIZE=10;
    private final Semaphore connections;
    //连接池
    private static LinkedList<Integer>pool;
    public SemphoreTest() {
        pool=new LinkedList<>();
        //初始化连接池
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(i);
        }
        this.connections = new Semaphore(POOL_SIZE);
    }
	//释放连接
    public void release(Integer conn){

        if(conn!=null) {
            System.out.println("当前有"+connections.getQueueLength()+"个线程等待数据库连接!!"
                    +"可用连接数:"+connections.availablePermits());
            synchronized (pool) {
                pool.addLast(conn);
            }
            connections.release();
        }
    }
	//获取连接
    public Integer acquire() throws InterruptedException {
        connections.acquire();
        Integer conn;
        synchronized (pool){
            conn=pool.removeFirst();
        }
        return conn;
    }

    public static void main(String[] args) {
        SemphoreTest dbPool=new SemphoreTest();
        ExecutorService pool = new ThreadPoolExecutor(50,100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
		//50个线程获取连接
        for (int i = 0; i < 50; i++) {
            pool.execute(()->{
                try {
                    Integer conn=dbPool.acquire();
                    System.out.println(Thread.currentThread().getName()
                            +"--------获取数据库连接");
                    Thread.sleep(1000);
                    System.out.println("归还连接");
                    dbPool.release(conn);
                } catch (InterruptedException e) {
                }
            });
        }

    }
}

结果:

pool-1-thread-1--------获取数据库连接
pool-1-thread-2--------获取数据库连接
pool-1-thread-3--------获取数据库连接
pool-1-thread-5--------获取数据库连接
pool-1-thread-4--------获取数据库连接
pool-1-thread-6--------获取数据库连接
pool-1-thread-7--------获取数据库连接
pool-1-thread-8--------获取数据库连接
pool-1-thread-9--------获取数据库连接
pool-1-thread-10--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-1--------获取数据库连接
归还连接
归还连接
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-5--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-3--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-2--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-4--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-7--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-6--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-9--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-8--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-10--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-1--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-5--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-4--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-2--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-3--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-7--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
归还连接
当前有0个线程等待数据库连接!!可用连接数:1
pool-1-thread-9--------获取数据库连接
pool-1-thread-6--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-8--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-10--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-1--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-5--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-4--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-2--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-3--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-7--------获取数据库连接
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-9--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-8--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-6--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-10--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-1--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-5--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-2--------获取数据库连接
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-3--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-7--------获取数据库连接
pool-1-thread-4--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-9--------获取数据库连接
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-6--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-8--------获取数据库连接
当前有0个线程等待数据库连接!!可用连接数:0
pool-1-thread-10--------获取数据库连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:0
归还连接
当前有0个线程等待数据库连接!!可用连接数:1
归还连接
当前有0个线程等待数据库连接!!可用连接数:2
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:3
当前有0个线程等待数据库连接!!可用连接数:3
归还连接
当前有0个线程等待数据库连接!!可用连接数:5
归还连接
当前有0个线程等待数据库连接!!可用连接数:6
归还连接
归还连接
当前有0个线程等待数据库连接!!可用连接数:7
当前有0个线程等待数据库连接!!可用连接数:8
归还连接
当前有0个线程等待数据库连接!!可用连接数:9

你可能感兴趣的:(并发编程,java,并发编程,semaphore,thread,java,多线程)