已经习惯了阿里面试官的冷笑:用过Semaphore吧,不妨说说?html
本质就是 信号量模型,模型图以下:linux
其中的 计数器 和 等待队列 对外部是透明的,仅能经过提供的三大方法访问它们。
详细说说哪三大方法?面试
init()
用于设置计数器的初始值。数据库
down()
计数器-1。若此时计数器<0,则当前线程被 阻塞。安全
up()
计数器+1。若此时计数器≤0,则唤醒 等待队列 中的一个线程,并将其从【等待队列】移除。有同窗可能会认为这里的判断条件应该≥0,估计你是理解成生产者-消费者模式中的生产者了。能够反过来想,>0 意味着没有阻塞的线程,因此只有 ≤0 时才须要唤醒一个等待的线程。并发
down()、up()应配对使用,并按序使用:ui
先调用down(),获取锁
执行处理完后,调用up(),释放锁
若信号量init值为1,并发场景下应该不会出现>0状况,除非故意调先用up(),但这也失去了信号量的意义。url
注意,这些方法都是原子性的,由信号量模型的实现方保证。JDK里的信号量模型就是由Semaphore实现,Semaphore保证了这三个方法都是原子操做。.net
talk is cheap,show me code?
信号量模型中的down()、up()最先被称为P操做和V操做,信号量模型也称PV原语。还有的人会用semWait()和semSignal()表达它们,叫法不一样,语义都相同。JUC的acquire()、release()分别对应down()和up()。线程
就像信号灯,必须先检查是否为绿灯才能经过。好比累加器,count+=1操做是个临界区,只容许一个线程执行,也就是说要保证互斥。
假设线程t一、t2同时访问add(),当同时调用acquire时,因为acquire是个原子操做,仅会有一个线程(假设t1)把信号量里的计数器减为0,t2则是将计数器减为-1:
对t1,信号量里面的计数器的值是0,≥0,因此t1不会被阻塞,而是继续执行
对t2,信号量里面的计数器的值是-1,<0,因此t2被阻塞
因此此时只有t1会进入临界区执行count+=1。
当t1执行release(),信号量里计数器的值是-1,加1以后的值是0,≤0,根据up(),此时等待队列中的t2会被唤醒。因而t2在t1执行完临界区代码后,才得到进入临界区执行的机会,这就保证了互斥。
既然有JDK提供了Lock,为啥还要提供一个Semaphore ?
实现互斥锁,仅是 Semaphore的部分功能,Semaphore还能够容许多个线程访问一个临界区。
最多见的就是各类池化资源,好比数据库链接池,同一时刻,容许多个线程同时使用链接池。每一个链接在被释放前,不容许其余线程使用。
对象池要求一次性建立出N个对象,以后全部的线程重复利用这N个对象,固然对象在被释放前,也是不容许其余线程使用的。因此核心就是限流器,这里的限流指不容许多于N个线程同时进入临界区。
如何快速实现一个这样的限流器呢?
那就是信号量。把计数器的值设置成对象池里对象的个数N便可:
注意这里使用的是 Vector,进入临界区的N个线程不安全。add/remove都是不安全的。好比 ArrayList remove() :
好的,请回家等通知吧!