Semaphore 原理简介和使用

Semaphore共享锁

简介

在多线程环境下用于协调各个线程, 以保证它们能够正确、合理的使用公共资源
信号量维护了一个许可集,我们在初始化Semaphore时需要为这个许可集传入一个数量值,
该数量值代表同一时间能访问共享资源的线程数量。
线程可以通过acquire()方法获取到一个许可,然后对共享资源进行操作,
如果许可集已分配完了,那么线程将进入等待状态,
直到其他线程释放许可才有机会再获取许可,线程释放一个许可通过release()方法完成 复制代码

DEMO了解其用法

上述示例说明:

在创建Semaphore时初始化5个许可,这也就意味着同一个时间点允许5个线程进行共享资源访问,
使用acquire()方法为每个线程获取许可,并进行休眠1秒,如果5个许可已被分配完,
新到来的线程将进入等待状态。如果线程顺利完成操作将通过release()方法释放许可,
我们执行代码,可以发现每隔1秒几乎同一时间出现5条线程访问 复制代码

Semaphore实现互斥锁

简介

在初始化信号量时传入1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。
这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可或零个可用的许可。
按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)

复制代码

DEMO了解其用法

创建一个数量为1的互斥信号量Semaphore,
然后并发执行10个线程,在线程中利用Semaphore控制线程的并发执行,
因为信号量数值只有1,因此每次只能一条线程执行,其他线程进入等待状态
复制代码

Semaphore提供的方法

  • Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的Semaphore

  • Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的Semaphore,true即为公平锁

  • void acquireUninterruptibly() 从此信号量中获取许可,不可中断

  • int availablePermits() 返回此信号量中当前可用的许可数

  • int drainPermits() 获取并返回立即可用的所有许可

  • protected Collection<Thread> getQueuedThreads() 返回一个 collection,包含可能等待获取的线程

  • int getQueueLength() 返回正在等待获取的线程的估计数目

  • boolean hasQueuedThreads() 查询是否有线程正在等待获取

  • boolean isFair() 如果此信号量的公平设置为 true,则返回 true

  • boolean tryAcquire() 仅在调用时此信号量存在一个可用许可,才从信号量获取许可

  • boolean tryAcquire(long timeout, TimeUnit unit) 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可

内部原理分析

类图

AQS是基础组件,只负责核心并发操作,如加入或维护同步队列,控制同步状态,等,
而具体的加锁和解锁操作交由子类完成,
因此子类Semaphore共享锁的获取与释放需要自己实现,
这两个方法分别是获取锁的tryAcquireShared(int arg)方法和释放锁的tryReleaseShared(int arg)方法,这点从Semaphore的内部结构完全可以看出来
复制代码
Semaphore的内部类公平锁(FairSync)和非公平锁(NoFairSync)各自实现不同的获取锁方法即tryAcquireShared(int arg),
毕竟公平锁和非公平锁的获取稍后不同,
而释放锁tryReleaseShared(int arg)的操作交由Sync实现,因为释放操作都是相同的,因此放在父类Sync中实现当然是最好的
复制代码

源码分析

默认非公平锁
复制代码
初始化信号量的时候 传入的 permits 参数 最终会赋值到aqs的state变量 
并对state进行cas操作

调用Semaphore的acquire()方法后,
执行过程是这样的,
当一个线程请求到来时,如果state值代表的许可数足够使用, 那么请求线程将会获得同步状态即对共享资源的访问权,并更新state的值(一般是对state值减1), 但如果state值代表的许可数已为0,则请求线程将无法获取同步状态, 线程将被加入到同步队列并阻塞, 直到其他线程释放同步状态(一般是对state值加1)才可能获取对共享资源的访问权 复制代码
先获取state的值,并执行减法操作,得到remaining值,
如果remaining大于等于0,那么线程获取同步状态成功,可访问共享资源,并更新state的值,
如果remaining小于0,那么线程获取同步状态失败,将被加入同步队列(通过doAcquireSharedInterruptibly(arg))

采用无锁(CAS)并发的操作保证对state值修改的安全
 复制代码
1、在方法中,由于当前线程没有获取同步状态,因此创建一个共享模式(Node.SHARED)的结点并通过addWaiter(Node.SHARED)加入同步队列,

2、加入完成后,当前线程进入自旋状态,首先判断前驱结点是否为head,

a、如果是,那么尝试获取同步状态并返回r值,如果r大于0,则说明获取同步状态成功,将当前线程设置为head并传播,传播指的是,同步状态剩余的许可数值不为0,通知后续结点继续获取同步状态,到此方法将会return结束,获取到同步状态的线程将会执行原定的任务。
 b、如果前驱结点不为head或前驱结点为head并尝试获取同步状态失败,那么调用shouldParkAfterFailedAcquire(p, node)方法判断前驱结点的waitStatus值是否为SIGNAL并调整同步队列中的node结点状态,如果返回true,那么执行parkAndCheckInterrupt()方法,将当前线程挂起并返回是否中断线程的flag  复制代码

在AQS中存在一个变量state,当我们创建Semaphore对象传入许可数值时,
最终会赋值给state,state的数值代表同一个时刻可同时操作共享数据的线程数量,
每当一个线程请求(如调用Semaphored的acquire()方法)获取同步状态成功,
state的值将会减少1,直到state为0时,表示已没有可用的许可数,
也就是对共享数据进行操作的线程数已达到最大值,其他后来线程将被阻塞, 此时AQS内部会将线程封装成共享模式的Node结点,加入同步队列中等待并开启自旋操作。 只有当持有对共享数据访问权限的线程执行完成任务并释放同步状态后, 同步队列中的对于的结点线程才有可能获取同步状态并被唤醒执行同步操作,注 意在同步队列中获取到同步状态的结点将被设置成head并清空相关线程数据(毕竟线程已在执行也就没有必要保存信息了), AQS通过这种方式便实现共享锁 复制代码

本文使用 mdnice 排版

猜你喜欢

转载自juejin.im/post/5ef864445188252e884e6227
今日推荐