参考: https://blog.csdn.net/javazejian/article/details/76167357
1.semaphore 也就是我们常说的信号灯,用来控制并发时线程的数量,实现了序列化接口。
通过 acquire 获取一个许可,如果没有就等待;
通过 release 释放一个许可。有点类似限流的作用。
Semaphore 只有3个操作:
(1).初始化;Semaphore 构造函数,传入信号数目,Semaphore 将信号数目设置到 AQS 的 state 上;
(2).acquire 获取信号,如果信号数目大于0,通过 CAS 将信号数减1, 获取成功;否则被阻塞,并将线程记录到 AQS 的等待锁队列中;
(3).release 释放信号,通过 CAS 将信号数加1,并唤醒等待队列上队首线程重新尝试获取信号。
2.semaphore 实现原理
Semaphore 内部其实是根据同步状态的值来限制并发的时线程的数量,当同步状态值为0时,后来的线程将被阻塞,直到有线程释放。
Semaphore 的锁是通过 Sync 这个类完成的,Sync 则继承自 AQS ,AQS 是独占锁和共享锁的父类,通过继承 AQS 实现共享锁。
Sync 有两个子类是 FairSync 和 NonfairSync,分别代表公平锁和非公平锁。
FairSync 判断是否有线程等待,如果没有则尝试获取信号,如果有则加入到等待队列。
NonfairSync 则先尝试获取锁,如果获取失败则再加入等待队列。
Semaphore 默认是非公平锁。
为什么使用非公平锁,这个是性能上的考虑,如果每次都去唤醒线程去获取信号,这是非常消耗资源的,非公平锁的性能和吞吐量也明显优于公平锁。
3.java.util.concurrent.Semaphore#acquire()
Semaphore 调用 acquire方法时,调用 AQS 的 acquireSharedInterruptibly 方法,AQS 则调用子类的 tryAcquireShared 方法,如果获取成功,则直接返回;如果获取失败,则调用调用 AQS 的方法 doAcquireSharedInterruptibly 阻塞并加入到等待队列等待唤醒。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
4.java.util.concurrent.Semaphore#release()
Semaphore 调用 release 方法时,调用 AQS 的 releaseShared 方法,AQS 则调用子类的 tryReleaseShared 方法,如果释放成功,则调用 doReleaseShared 方法唤醒等待队列队首线程,线程启动后,如果 tryAcquireShared 返回值大于等于 0,则通过 setHeadAndPropagate 方法进行传播,唤醒下一个线程。
public void release() {
sync.releaseShared(1);
}
总结:
Semaphore获取许可(调用acquire()方法),最终会调用到 AQS中的方法,操作的是 state 的值减1,如果获取许可失败则进入阻塞队列;
Semaphore释放许可(调用release()方法),最终也是进入 AQS中的方法,操作 state加1,如果调用成功则会唤醒阻塞队列队首线程。
抢占锁:实际上就是进行CAS操作,尝试改变 AQS 中 state 的值。
公平锁和非公平锁的区别:先判断队列是否有元素再尝试获取锁则是公平的;上来就尝试抢占锁就是非公平的;
共享锁:资源不足时,线程将被封装共享模式的Node结点加入同步队列等待。共享模式,允许多个线程同时获取同步状态
java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
Semaphore实现互斥锁:在初始化信号量时传入1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。
无锁操作:for死循环加CAS操作,保证线程安全。
使用示例:
package com.cmz.concurrent;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author chen.mz
* @email 1034667543@qq.com
* @nickname 陈梦洲
* @date 2019/9/10
* @description Semaphore Demo
* <p>信号量:常用于限制可以访问某些资源的线程数量</p>
* <p>可以理解为:我们的停车场最多允许停 5 辆车,那么当 semaphore.acquire() 发放完毕,其他的车辆进入不了(线程就阻塞)</p>
* <p>当有车辆离开时会归还停车卡 semaphore.release(),此时阻塞的就可以被唤醒,可以继续进入停车场</p>
*/
public class SemaphoreDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许 3个线程同时访问
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
final long num = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
// 获取许可(拿到停车卡)
semaphore.acquire();
// do something
System.out.println("Accessing: " + num);
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
// 释放许可(还回停车卡)
semaphore.release();
System.out.println("Release..." + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}