Java并发之Semaphore

JDK1.5的并发包java.util.concurrent中提供了几个非常有用的工具类,这些工具类给我们在业务开发过程中提供了一种并发流程控制的手段,本文会基于实际应用场景介绍如何使用Semaphore,以及内部实现机制。

Semaphore是什么?

所谓Semaphore即 信号量 的意思,
这个叫法并不能更好的展示它的作用,更形象的说法应该是许可证管理器
它可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

Semaphore内部维护了一组虚拟的许可,许可证的数量可以通过构造函数的参数指定。

  • 访问特定资源前,必须使用acquire方法获得许可证,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
  • 访问资源后,使用release释放许可证。

SemaphoreReentrantLock类似,获取许可证有公平策略和非公平许可策略,默认情况下使用非公平策略。

Semaphore方法说明

Semaphore的方法如下:

——Semaphore(permits)

初始化许可证数量的构造函数

——Semaphore(permits,fair)

初始化许可证数量和是否公平模式的构造函数

——isFair()

是否公平模式FIFO

——availablePermits()

获取当前可用的许可证数量

——acquire()

当前线程尝试去阻塞的获取1个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了1个可用的许可证,则会停止等待,继续执行。
当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
——acquire(permits)

当前线程尝试去阻塞的获取permits个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了n个可用的许可证,则会停止等待,继续执行。
当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
——acquireUninterruptibly()

当前线程尝试去阻塞的获取1个许可证(不可中断的)。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了1个可用的许可证,则会停止等待,继续执行。
——acquireUninterruptibly(permits)

当前线程尝试去阻塞的获取permits个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了n个可用的许可证,则会停止等待,继续执行。
——tryAcquire()

当前线程尝试去获取1个许可证。

此过程是非阻塞的,它只是在方法调用时进行一次尝试。

如果当前线程获取了1个可用的许可证,则会停止等待,继续执行,并返回true。

如果当前线程没有获得这个许可证,也会停止等待,继续执行,并返回false。

——tryAcquire(permits)

当前线程尝试去获取permits个许可证。

此过程是非阻塞的,它只是在方法调用时进行一次尝试。

如果当前线程获取了permits个可用的许可证,则会停止等待,继续执行,并返回true。

如果当前线程没有获得permits个许可证,也会停止等待,继续执行,并返回false。

——tryAcquire(timeout,TimeUnit)

当前线程在限定时间内,阻塞的尝试去获取1个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了可用的许可证,则会停止等待,继续执行,并返回true。
当前线程等待时间timeout超时,则会停止等待,继续执行,并返回false。
当前线程在timeout时间内被中断,则会抛出InterruptedException一次,并停止等待,继续执行。
——tryAcquire(permits,timeout,TimeUnit)

当前线程在限定时间内,阻塞的尝试去获取permits个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

当前线程获取了可用的permits个许可证,则会停止等待,继续执行,并返回true。
当前线程等待时间timeout超时,则会停止等待,继续执行,并返回false。
当前线程在timeout时间内被中断,则会抛出InterruptedException一次,并停止等待,继续执行。
——release()

当前线程释放1个可用的许可证。

——release(permits)

当前线程释放permits个可用的许可证。

——drainPermits()

当前线程获得剩余的所有可用许可证。

——hasQueuedThreads()

判断当前Semaphore对象上是否存在正在等待许可证的线程。

——getQueueLength()

获取当前Semaphore对象上是正在等待许可证的线程数量。

应用场景

Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore流量
控制
(限制获取某种资源的线程数量)。

import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Semaphore 测试方法
 * @description:
 * @author: ArvinWoo
 * @date: 2019年7月17日下午5:30:33
 */
public class SemaphoreTest {
	//线程数量
	private static final int COUNT = 40;
	//信号量 最大许可证为10个
	private static Semaphore semaphore = new Semaphore(10);
	
	public static void main(String[] args) {
		for (int i = 0; i < COUNT; i++) {
			Executors.newFixedThreadPool(COUNT).execute(new SemaphoreTest.Task());
		}
	}

	static class Task implements Runnable {
		@Override
		public void run() {
			try {
				// 读取文件操作( 当前线程尝试去阻塞的获取1个许可证。)
				semaphore.acquire();
				System.out.println("执行操作");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				// 存数据过程 (当前线程释放1个可用的许可证。)
				semaphore.release();
			}
		}
	}
}
模拟学校食堂的窗口打饭过程
  • 学校食堂有2个打饭窗口
  • 学校中午有20个学生 按次序 排队打饭
  • 每个人打饭时耗费时间不一样
  • 有的学生耐心很好,他们会一直等待直到打到饭
  • 有的学生耐心不好,他们等待时间超过了心里预期,就不再排队,而是回宿舍吃泡面了
  • 有的学生耐心很好,但是突然接到通知,说是全班聚餐,所以也不用再排队,而是去吃 大餐了
分析
  • 食堂有2个打饭窗口:需要定义一个permits=2的Semaphore对象。
  • 学生 按次序 排队打饭:此Semaphore对象是公平的。
  • 有20个学生:定义20个学生线程。
  • 打到饭的学生:调用了acquireUninterruptibly()方法,无法被中断
  • 吃泡面的学生:调用了tryAcquire(timeout,TimeUnit)方法,并且等待时间超时了
  • 吃大餐的学生:调用了acquire()方法,并且被中断了
package com.hsw.test.concurrent;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.RandomUtils;

/**
 * Semaphore 测试方法
 * 
 * @description:
 * @author: ArvinWoo
 * @date: 2019年7月17日下午5:30:33
 */
public class SemaphoreTest {
	// 2个打饭窗口 公平队列
	private static Semaphore semaphore = new Semaphore(2, true);

	public static void main(String[] args) throws InterruptedException {
		Thread[] class101 = new Thread[5];
		for (int i = 0; i < 20; i++) {
			// 前10个同学都在耐心的等待打饭
			if (i < 10) {
				new Thread(new Students("打饭学生" + i, semaphore, 0)).start();
			} else if (i >= 10 && i < 15) {// 这5个学生没有耐心打饭,只会等1000毫秒
				new Thread(new Students("泡面学生" + i, semaphore, 1)).start();
			} else {
				// 这5个学生没有耐心打饭
				class101[i - 15] = new Thread(new Students("聚餐学生" + i, semaphore, 2));
				class101[i - 15].start();
			}
		}
		//
		Thread.sleep(5000);
		for (int i = 0; i < 5; i++) {
			class101[i].interrupt();
		}
	}

	static class Students implements Runnable {

		// 学生姓名
		private String name;
		// 打饭许可证
		private Semaphore semaphore;
		// 打饭方式
		// 0 一直等待直到打到饭
		// 1 等了一会不耐烦了,回宿舍吃泡面了
		// 2 打饭中途被其他同学叫走了,不再等待
		private int type;

		// 构造
		public Students(String name, Semaphore semaphore, int type) {
			this.name = name;
			this.semaphore = semaphore;
			this.type = type;
		}

		@Override
		public void run() {
			// 根据打饭情形分别进行不同的处理
			switch (type) {
			// 打饭时间
			// 这个学生很有耐心,它会一直排队直到打到饭
			case 0:
				// 排队
				semaphore.acquireUninterruptibly();
				// 进行打饭
				try {
					Thread.sleep(RandomUtils.nextLong(1000, 3000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 将打饭机会让后后面的同学
				semaphore.release();
				// 打到了饭
				System.out.println(name + " 终于打到了饭.");
				break;

			// 这个学生没有耐心,等了1000毫秒没打到饭,就回宿舍泡面了
			case 1:
				// 排队
				try {
					// 如果等待超时,则不再等待,回宿舍吃泡面
					if (semaphore.tryAcquire(RandomUtils.nextInt(6000, 16000), TimeUnit.MILLISECONDS)) {
						// 进行打饭
						Thread.sleep(RandomUtils.nextLong(1000, 3000));
						// 将打饭机会让后后面的同学
						semaphore.release();
						// 打到了饭
						System.out.println(name + " 终于打到了饭.");
					} else {
						// 回宿舍吃泡面
						System.out.println(name + " 回宿舍吃泡面.");
					}
				} catch (InterruptedException e) {
				}
				break;
			// 这个学生也很有耐心,但是他们班突然宣布聚餐,它只能放弃打饭了
			case 2:
				// 排队
				try {
					semaphore.acquire();
					// 进行打饭
					Thread.sleep(RandomUtils.nextLong(1000, 3000));
					// 将打饭机会让后后面的同学
					semaphore.release();
					// 打到了饭
					System.out.println(name + " 终于打到了饭.");
				} catch (InterruptedException e) {
					System.out.println(name + " 全部聚餐,不再打饭.");
				}
				break;
			default:
				break;
			}
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值