12、JUC源码分析:Semaphore基础篇

JUC-AQS原理篇
JUC-AQS源码篇
JUC-AQS的Condition之await和signal源码解析

JUC-CountDownLatch基础篇
JUC-CountDownLatch源码分析
JUC-Semaphore基础篇
JUC-Semaphore源码分析
JUC-ReentrantReadWriteLock锁基础篇
JUC-ReentrantReadWriteLock锁源码分析
JUC-ReentrantLock锁基础篇
JUC-ReentrantLock锁源码分析
JUC-CyclicBarrier基础篇
JUC-CyclicBarrier源码分析

文章目录

    1. 概念
    • 1.1 Semaphore是什么
    • 1.2使用场景
  • 2.常用方法
  • 3.例子

1. 概念

1.1 Semaphore是什么

Semaphore顾名思义叫做信号量,是操作系统中的一个概念。在java并发编程中,Semaphore本质上是一个共享锁的,用来限制多线程对共享资源的并发访问。
Semaphore内部维护了一个计数器state,其值代表了可以访问的共享资源的个数。一个线程如果要访问1个(n个)共享资源,需要先去获得1个(n个)信号量,如果信号量的计数器值大于1个(n个),意味着有共享资源可以访问,则使其计数器state值减去1个(n个),再访问共享资源。使用完后,再将其获得的共享资源个数释放。如果计数器值不满足,则此线程进入休眠。当其他的线程使用完共享资源后,释放信号量,之前进入休眠的线程将被唤醒并再次试图获得信号量。

1.2使用场景

Semaphore的使用场景主要是流量控制,比如数据库连接数,使用相同的数据库,会有连接数的控制。当连接到达了限制数量后,后面的线程只能排队等前面的线程释放数据库连接后才能获得数据库连接。
在比如游乐园的人数限流,停车场停车服务等。

2.常用方法

Semaphore提供的一些常用的方法有:

方法 说明
acquire() 从Semaphore中获取一个信号量,如果获取不到,线程进入同步队列中阻塞等待,直到其他线程释放了一个许可证或者当前线程被中断。
release() 释放一个信号量 ,并唤醒同步队列中阻塞的线程,重新来获取信号量
acquire(int permits) 从Semaphore中获取permits个信号量,如果获取不到,线程进入同步队列中阻塞等待,直到其他线程释放了一个许可证或者当前线程被中断。
release(int permits) 释放permits个信号量 ,并唤醒同步队列中阻塞的线程,重新来获取信号量
acquireUninterruptibly() 从Semaphore中获取1个信号量,如果获取不到,线程进入同步队列中阻塞等待,直到其他线程释放了一个许可证(不响应线程中断,即此线程在阻塞的时候,被中断了。那么在它被唤醒的时候,会调用自身的interrupt()方法来将线程中断标志物置位true。而调用acquire()方法的现场,在阻塞的时候,被中断了。那么在它被唤醒的时候,则会抛出InterruptedException)。
acquireUninterruptibly(int permits) 从Semaphore中获取permits个信号量。同acquireUninterruptibly()
tryAcquire() 从Semaphore中尝试获取一个信号量,获取成功则返回true,获取失败则返回false,不会进行等待。(不受公平策略的影响,信号量可用则立即获得)
tryAcquire(int permits) 从Semaphore中尝试获取permits个信号量,获取成功则返回true,获取失败则返回false,不会进行等待。(不受公平策略的影响,信号量可用则立即获得)
tryAcquire(long timeout, TimeUnit unit) 从Semaphore中尝试获取一个信号量,获取成功则返回true,获取失败则等待指定的时间,直到等待时间结束还是没有获取到许可证则返回false。不会进行等待(不受公平策略的影响,信号量可用则立即获得)
tryAcquire(int permits,long timeout, TimeUnit unit) 从Semaphore中尝试获取permits个信号量,获取成功则返回true,获取失败则等待指定的时间,直到等待时间结束还是没有获取到许可证则返回false,不会进行等待。(不受公平策略的影响,信号量可用则立即获得)
availablePermits() 返回当前还剩下都少个可用的信号量)
drainPermits() 返回当前还剩下都少个可用的信号量,并全部消耗掉这些可用的信号量,即将state的值更新为0)
reducePermits(int reduction) 将当前还剩下的信号量消耗掉reduction个即将state的值减reduction)

3.例子

下面用停车场的业务场景来进行举例。
假设一个停车场有10个停车位(共享资源),保安亭有10张停车票(信号量)。每个司机要进入停车场停车时,要先去保安亭那里抢1张停车票。当司机抢到了1张停车票时(保安亭里面剩余的停车票大于0),才进到停车场里面停车。没有抢到则到外面进行排队等待(j进入了等待队列中)。司机离开时,将这1张停车票还给保安亭,并对外面的人说有票了,你们赶快来取(这里面涉及到了一个公平策略的问题)。
假设现在有这样一种场景:当司机离开时将这1张停车票还给保安亭,并对外面的人说有票了,你们赶快来取。按理说,应该是排队等待的队列中排在第一位的司机来取这张停车票。如果这时候刚好新来了一个新的司机要进入停车场停车,也来到保安亭中取停车票,那么这张停车票应该给谁?
公平锁:当Semaphore是以公平策略的方式创建出来的,这张停车票给排在第一位的司机,新来的司机老老实实的去队列后面排队去。
非公平锁:当Semaphore是以非公平策略的方式创建出来的,那么就是新来的司机跟排在第一位的司机抢起来,谁抢到了,就是谁的。

public class SemaphoreTest {
   
     
    public static void main(String[] args) throws InterruptedException {
   
     
        //公平策略
        Semaphore semaphore = new Semaphore(10, true);
        //15个司机来抢10个停车位
        for (int i = 1; i <=15; i++) {
   
     
            new Thread("司机"+i){
   
     
                @Override
                public void run() {
   
     
                    if (semaphore.availablePermits() > 0){
   
     
                        System.out.print(Thread.currentThread().getName() + "号要来停车场停车," + "还有停车票,不用排队等待。\n");
                    } else {
   
     
                        System.out.print(Thread.currentThread().getName() + "号要来停车场停车," + "停车票没了,要去排队等着了。\n");
                    }
                    try {
   
     
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() +"号进入了停车场");
                        //在停车场停车
                        Thread.sleep(new Random().nextInt(10000));
                        semaphore.release();
                        System.out.println(Thread.currentThread().getName() +"号离开了停车场");
                    } catch (InterruptedException e) {
   
     

                    }

                }
            }.start();
        }
    }
}

公平策略执行结果,可以看到司机11-15号是按照他们来的顺序进入停车场的

司机2号要来停车场停车,还有停车票,不用排队等待。
司机4号要来停车场停车,还有停车票,不用排队等待。
司机2号进入了停车场
司机1号要来停车场停车,还有停车票,不用排队等待。
司机3号要来停车场停车,还有停车票,不用排队等待。
司机1号进入了停车场
司机3号进入了停车场
司机5号要来停车场停车,还有停车票,不用排队等待。
司机4号进入了停车场
司机6号要来停车场停车,还有停车票,不用排队等待。
司机5号进入了停车场
司机6号进入了停车场
司机7号要来停车场停车,还有停车票,不用排队等待。
司机8号要来停车场停车,还有停车票,不用排队等待。
司机7号进入了停车场
司机8号进入了停车场
司机9号要来停车场停车,还有停车票,不用排队等待。
司机9号进入了停车场
司机10号要来停车场停车,还有停车票,不用排队等待。
司机10号进入了停车场
司机11号要来停车场停车,停车票没了,要去排队等着了。
司机12号要来停车场停车,停车票没了,要去排队等着了。
司机13号要来停车场停车,停车票没了,要去排队等着了。
司机14号要来停车场停车,停车票没了,要去排队等着了。
司机15号要来停车场停车,停车票没了,要去排队等着了。
司机5号离开了停车场
司机11号进入了停车场
司机3号离开了停车场
司机12号进入了停车场
司机7号离开了停车场
司机13号进入了停车场
司机6号离开了停车场
司机14号进入了停车场
司机2号离开了停车场
司机15号进入了停车场
司机11号离开了停车场
司机9号离开了停车场
司机12号离开了停车场
司机1号离开了停车场
司机8号离开了停车场
司机15号离开了停车场
司机4号离开了停车场
司机10号离开了停车场
司机13号离开了停车场
司机14号离开了停车场

非公平策略执行结果,可以看到司机12号比司机14号后排队,却比14早进入停车场。

司机2号要来停车场停车,还有停车票,不用排队等待。
司机1号要来停车场停车,还有停车票,不用排队等待。
司机2号进入了停车场
司机1号进入了停车场
司机3号要来停车场停车,还有停车票,不用排队等待。
司机3号进入了停车场
司机4号要来停车场停车,还有停车票,不用排队等待。
司机4号进入了停车场
司机5号要来停车场停车,还有停车票,不用排队等待。
司机5号进入了停车场
司机6号要来停车场停车,还有停车票,不用排队等待。
司机6号进入了停车场
司机7号要来停车场停车,还有停车票,不用排队等待。
司机7号进入了停车场
司机8号要来停车场停车,还有停车票,不用排队等待。
司机9号要来停车场停车,还有停车票,不用排队等待。
司机8号进入了停车场
司机9号进入了停车场
司机10号要来停车场停车,还有停车票,不用排队等待。
司机10号进入了停车场
司机11号要来停车场停车,还有停车票,不用排队等待。
司机13号要来停车场停车,停车票没了,要去排队等着了。
司机14号要来停车场停车,停车票没了,要去排队等着了。
司机12号要来停车场停车,停车票没了,要去排队等着了。
司机15号要来停车场停车,停车票没了,要去排队等着了。
司机3号离开了停车场
司机11号进入了停车场
司机7号离开了停车场
司机13号进入了停车场
司机4号离开了停车场
司机12号进入了停车场
司机2号离开了停车场
司机14号进入了停车场
司机13号离开了停车场
司机15号进入了停车场
司机12号离开了停车场
司机6号离开了停车场
司机8号离开了停车场
司机9号离开了停车场
司机11号离开了停车场
司机10号离开了停车场
司机5号离开了停车场
司机1号离开了停车场
司机14号离开了停车场
司机15号离开了停车场