信号量Semaphore

信号量Semaphore是《java并发编程实战》里讲到的同步工具类的第三种。用来控制线程并发数量

主要构造方法:

public Semaphore(int permits, boolean fair)
public Semaphore(int permits)

构造Semaphore对象时,指定许可证总数,参数fair表示是否使用公平锁,如果使用的话,线程获取许可证的顺序将按照线程申请的时间顺序来进行,否则为随机获取,默认为非公平锁。

这边转一个大佬比较形象的说法:

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合

补充一点的是,单个信号量的Semaphore对象实现的互斥锁是不可重入的,这是和synchronized关键字不同的地方。

先直接上代码:

public class SemaPhoreTest {
	
	public static Semaphore s = new Semaphore(2, true);

	public static void main(String[] args) throws InterruptedException {
		
		s.tryAcquire(10, TimeUnit.SECONDS);
		System.out.println("可用许可证:"+s.availablePermits());
		Thread t1 = new Thread(new TestThread());
		t1.start();
		Thread.sleep(2000);
		System.out.println("等待线程队列长度:"+s.getQueueLength());
		s.release(2);
		Thread.sleep(2000);
		System.out.println("等待线程队列长度:"+s.getQueueLength());
	}

}

class TestThread implements Runnable{

	@Override
	public void run() {
		try {
			SemaPhoreTest.s.acquire(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
}

这里首先定义一个大小为2的信号量对象,先在主线程里获取一个信号量,使用的是public boolean tryAcquire(long timeout, TimeUnit unit)方法,获取一个信号量,但是有等待时间,如果超时了仍未获取到信号量,则返回false,获取信号量成功则返回true。
启动自己定义的线程,通过public void acquire(int permits) throws InterruptedException方法获得2个信号量(acquire方法如果不加参数,默认就是获取一个信号量),此时信号量对象只剩下一个,所以线程t1会进入阻塞状态,此时调用public final int getQueueLength()方法就会获得当前处于阻塞状态的线程队列的长度。
当主线程调用release方法释放信号量(和acquire方法相同,无参数就是释放1个)之后,线程t1则可能解除阻塞状态获得信号量,此时再查看阻塞状态的线程队列的长度就为0了。
该代码输出如下:

可用许可证:1
等待线程队列长度:1
等待线程队列长度:0

正常来讲,acquire方法获取信号量的方法是可被中断的,所以除此之外,还有一个public void acquireUninterruptibly(int permits)方法,可以无视中断去获取信号量,代码就不上了。

在《java并发编程实战》这本书里针对Semaphore的用处举了两个例子,一个是有界容器(书里的例子是Set),一个是资源池(BlockingQueue也能实现同样效果)。下面我自己大概简单模拟了一个资源池代码,多个线程分别去获得资源(信号量)进行操作,如果等待超时仍没有获得资源,则会取消资源申请而返回:

public class SemaphorePoolTest {
	
	public static Semaphore semaphore = new Semaphore(2);
	
	public static Random random = new Random();
	
	static class PoolThread implements Runnable{

		@Override
		public void run() {
			try {
				if(semaphore.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
					System.out.println(Thread.currentThread().getName()
							+"获得资源池中的资源......");
					Thread.sleep(random.nextInt(1500));
					semaphore.release();
					System.out.println(Thread.currentThread().getName()+"已释放资源......");
				}else {
					System.out.println(Thread.currentThread().getName()+"等待时间过长,不再获取资源池中资源......");
				}
			}catch (InterruptedException e) {
				System.out.println(Thread.currentThread().getName()+"被中断......");
			}finally {
				
			}
		}
		
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new PoolThread());
		Thread t2 = new Thread(new PoolThread());
		Thread t3 = new Thread(new PoolThread());
		Thread t4 = new Thread(new PoolThread());
		Thread t5 = new Thread(new PoolThread());
		Thread t6 = new Thread(new PoolThread());
		Thread t7 = new Thread(new PoolThread());
		Thread t8 = new Thread(new PoolThread());
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		t6.start();
		t7.start();
		t8.start();
		
		t3.interrupt();

	}

}

运行结果(每次运行结果是不固定的):

Thread-7获得资源池中的资源......
Thread-0获得资源池中的资源......
Thread-2被中断......
Thread-7已释放资源......
Thread-6获得资源池中的资源......
Thread-0已释放资源......
Thread-5获得资源池中的资源......
Thread-4等待时间过长,不再获取资源池中资源......
Thread-3等待时间过长,不再获取资源池中资源......
Thread-1等待时间过长,不再获取资源池中资源......
Thread-5已释放资源......
Thread-6已释放资源......

下面引用一位大佬的文章,Semaphore里的其他方法:

availablePermits()  方法在前面用过,表示返回 Semaphore 对象中的当前可用许可数,此方法通常用于调试,因为许可数量(通路)可能是实时在改变的。

  drainPermits() 方法可获取并返回立即可用的所有许可(通路)个数,并将可用许可置为0。

  getQueueLength() 获取等待许可的线程个数。

  hasQueuedThreads() 判断有没有线程在等待这个许可。

  getQueueLength()hasQueuedThreads() 都是在判断当前有没有等待许可的线程信息时使用。

值得一提的是,Semaphore没有硬性规定acquire方法一定要在release方法之前,如果没有acquire就release一个信号量也是可行的,会使得信号量总量加1。jdk官方的说法是Semaphore对象的正确使用由程序去控制,也算是甩锅了。

———————————————————————————————————

我的一点吐槽:
虽然《java并发编程实战》这本书被称赞为java并发编程圣经,但是目前看到接近三分之一的样子,感觉一是翻译的比较拗口,二是有点偏理论,讲的不算特别深入,有些东西也是自己在网上找的,三是没有深入到底层代码。不过目前我也没有抠底层代码的打算,还是先把这本书的一周目看完再说。立个flag。

参考:
https://blog.csdn.net/baidu_23086307/article/details/56677898
https://www.cnblogs.com/XHJT/p/3910406.html
https://www.cnblogs.com/klbc/p/9500947.html
感谢大佬带飞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值