Semaphore原理浅析和相关面试题分析
Posted HelloWorld_EE
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Semaphore原理浅析和相关面试题分析相关的知识,希望对你有一定的参考价值。
本文首发在个人公众号:HelloWorldEE,欢迎关注。
本篇文章的来源是这样,有一天,我一同学面试某公司回来,和我分享其被问的相关面试题。其中就有一道关于Semaphore的面试题,个人觉得比较经典,分享出来供大家参考。
具体同学和面试官的对话还原出来是这样。
面试官:现在有一个方法task,希望只能被10个线程调用,利用Java相关类,应该如何来实现?
同学:使用Java中的Semaphore类来实现,当一个线程调用方法task之前先从Semaphore申请令牌,如果申请到了,则调用task方法,调用完之后释放令牌供其他的线程使用,如果没有,则阻塞等待。
面试官:Semaphore中申请令牌、释放令牌的方法叫什么?
同学:acquire、release方法
面试官:semaphore初始化有10个令牌,11个线程同时各调用1次acquire方法,会发生什么?
同学:拿不到令牌的线程阻塞,不会继续往下运行。
面试官.semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?
同学:稍微有点蒙,因为考虑到了锁的重入问题,不知道令牌会不会和锁一样,是可以重入的。注:笔者留给大家思考,后面会给出答案。
面试官:semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?
同学:额,比较蒙,一个线程获取一个令牌然后释放两个令牌,Semaphore会允许吗?同学回答应该不允许拿一个令牌然后释放多个令牌。注:笔者留给大家思考,后面会给出答案。
面试官.semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?
同学:额,继续蒙。。。心里想:不调用acquire方法来获取令牌,直接调用release方法来释放令牌,semaphore允许吗?
到这里,关于Semaphore的面试题就结束了,一问接一问,一环接一环,确实如果对Semaphore这个类没有特别细致的研究,回答起来是比较困难的。除了这个问题,还有其他的问题也相当经典,在后续的文章中,笔者会一一分析出来给大家分享,这样一场精彩的面试,为同学和面试官点赞。
下面回到Semaphore类,关于J.C.U包下Semaphore这个类,作为Java程序员,应该或许都会听说过和使用过。
这个类的作用:常常被用来控制访问速率。
例如:对于某一种资源,我们希望其最多被N个线程同时访问。
真实的场景:库存秒杀限流。
具体代码实现如下:
public class TestSemaphore
public static void main(String[] args)
ExecutorService executorService = Executors.newFixedThreadPool(10);
final Semaphore semaphore = new Semaphore(2);
for (int index = 0; index < 10; index++)
final int taskId = index;
executorService.submit(new Runnable()
@Override
public void run()
try
//1.获取执行令牌,如果获取不到,则阻塞
semaphore.acquire();
//2.执行任务
task();
//3.释放令牌
semaphore.release();
catch (InterruptedException e)
e.printStackTrace();
);
executorService.shutdown();
public static void task()
try
System.out.println("task开始执行");
Thread.sleep(1000);
System.out.println("task执行结束");
catch (Exception e)
e.printStackTrace();
看了上面的例子,对于Semaphore的使用,是比较简单的。其大致过程如下:
1.调用acquire来获取令牌,如果获取到,则继续运行,运行完成之后,调用release方法释放令牌供后面的线程使用。
2.如果获取不到,则等待,直到有令牌空闲出来,其才会被唤醒然后获取令牌之后继续运行。
通过上面的示例,怎么使用,我们应该都清楚了。那么对于acquire、release方法的实现原理是怎么样的呢?如果有兴趣,你可以去看一眼这个类的源码,比较简单,就是借助了一个volatile的变量state,然后当调用acquire方法是,就是对state进行减一操作,由于state–操作不是原子性操作,因为为保证原子性,采用的是cas来完成。当调用release,就是对state进行加一操作,也是采用的cas来完成。
下面是acquire、release方法的部分核心代码。
final int nonfairTryAcquireShared(int acquires)
for (;;)
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
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");
if (compareAndSetState(current, next))
return true;
看完相关的实现原理,简单来说:semaphore就是一个计算器。更具体、详细的源码分析可以参考相应的文章。
回到文章开头的面试题
问题1.semaphore初始化有10个令牌,11个线程同时各调用1次acquire方法,会发生什么?
答案:拿不到令牌的线程阻塞,不会继续往下运行。
问题2.semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?
答案:线程阻塞,不会继续往下运行。可能你会考虑类似于锁的重入的问题,很好,但是,令牌没有重入的概念。你只要调用一次acquire方法,就需要有一个令牌才能继续运行。
问题3.semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?
答案:能,原因是release方法会添加令牌,并不会以初始化的大小为准。
问题4.semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?
答案:能,原因是release会添加令牌,并不会以初始化的大小为准。Semaphore中release方法的调用并没有限制要在acquire后调用。
具体示例如下,如果不相信的话,可以运行一下下面的demo,在做实验之前,笔者也认为应该是不允许的。。
public class TestSemaphore2
public static void main(String[] args)
int permitsNum = 2;
final Semaphore semaphore = new Semaphore(permitsNum);
try
System.out.println("availablePermits:"+semaphore.availablePermits()+",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):"+semaphore.tryAcquire(3,1, TimeUnit.SECONDS));
semaphore.release();
System.out.println("availablePermits:"+semaphore.availablePermits()+",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):"+semaphore.tryAcquire(3,1, TimeUnit.SECONDS));
catch (Exception e)
到这里,关于Semaphore的分析,以及常见面试题的分析就结束了,希望对大家有一定的收获。
以上是关于Semaphore原理浅析和相关面试题分析的主要内容,如果未能解决你的问题,请参考以下文章