java Semaphore使用
生命猿于运动 人气:0简介
semaphore
中文意思既是信号量,它的主要功能就是用来控制某个资源同时被访问的线程数。
为了控制某块资源的并发访问量时,可以使用Semaphore
对象中的acquire()
方法获取访问令牌,如果Semaphore
对象访问令牌已发完,那么当前获取令牌的线程将会进入阻塞,带其他线程进行release()
释放令牌时,当前线程才有机会获得令牌从而拥有访问权限。
简述实现原理
Semaphore
实际上是一种共享锁,因为它允许多个线程并发获取共享的资源。在Semaphore
对象创建时必须设置可用令牌的初始数量permits
,用于控制并发时同时获取资源权限的线程数量。在Semaphore
类中继承了同步队列AbstractQueuedSynchronizer
,在此类中有个属性state
用于标记当前并发的队列数,也就是获取令牌的线程数,那么在进行acquire()
的时候,就会尝试获取共享锁,获取锁成功后state
值将加1,如果state
值已经达到permits
时就表示令牌已派发完,当前线程将进入阻塞状态,待其他线程进行release()
时state
值将减1,此时就会从队列中获取头部线程进行唤醒让其获得令牌进行资源访问。
如下图,仔细查看源码就会发现Semaphore
实际上就是重写了AbstractQueuedSynchronizer
中的tryAcquireShared()
、tryReleaseShared()
方法来实现的。
方法介绍
Semaphore
重载了两个构造函数,其一是Semaphore(int permits)
直接指定令牌数,默认为非公平锁;其二是Semaphore(int permits,boolean fair)
,fair参数即表示线程抢占令牌的公平性,true为公平锁,否则为非公平锁。acquire()
无参表示默认获取一个令牌,acquire(int permits)
表示获取指定permits
数量的令牌数,如果令牌不够,则当前线程进入阻塞状态。
tryAcquire()
无参表示尝试获取一个令牌,该方法是非阻塞的,所以如果令牌数不够获取失败返回false,否则就返回true;同时也重载了方法tryAcquire(int permits)
指定获取令牌数,tryAcquire(int permits, long timeout, TimeUnit unit)
在有效时间内尝试获取指定数量的令牌数,如果超时仍未获取到令牌则返回false,否则返回true。 release
同上支持无参与带参指定释放令牌数的方法。 drainPermits()
获取剩下的可用令牌。 hasQueuedThread()
用于判断当前Semaphare实例中是否存在等待获取令牌的线程。
案例分析
分析一下下面这段简短的代码,首先是创建一个信号量为5的Semaphore
对象,然后在创建一个线程池(这里为了演示方便,实际开发中不建议使用此线程池创建),利用for循环并发运行100个线程,当线程运行时优先获取一个令牌,在线程中的业务代码里我们做了1秒的休眠,为了展示等待获取令牌的效果,在延迟1秒执行完业务代码时进行令牌释放,后续的线程才能逐个被唤醒获取令牌访问共享资源。
@Slf4j public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(5); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { executorService.execute(() -> { try { semaphore.acquire(); log.info("成功获取令牌"); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("释放令牌"); semaphore.release(); } }); } executorService.shutdown(); } }
- 运行结果
由下图运行结果可见,100个线程并发并不是一次性都执行完的,而是要等待前面的线程释放令牌后等待的线程才可以获取令牌进行业务代码的运行。
适用场景
Semaphore
主要是运用在多线程环境中对某一些共享资源的访问量限制,防止多个线程并发访问同一资源,可能会导致大多数线程获取资源时都需要进行加锁,那如果是获取数据库中的数据,那么就可以缓解数据库的压力。
另一种情况是用于多线程运行的一个流量限制,一般情况下我们可能会通过线程池做一步线程数的控制,但是某些业务为了减轻CPU的负担,还是会做一些同时运行线程数的限制。
加载全部内容