Java多线程-并发工具类(三)控制并发线程数的Semaphore

参考:http://ifeve.com/concurrency-semaphore/

https://www.cnblogs.com/feijishuo/p/4538814.html

https://zhuanlan.zhihu.com/p/29463581

简介

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。很多年以来,我都觉得从字面上很难理解Semaphore所表达的含义,只能把它比作是控制流量的红绿灯,比如XX马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入XX马路,但是如果前一百辆中有五辆车已经离开了XX马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。

应用场景

Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控。

Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

public Semaphore(int permits) {          //参数permits表示许可数目,即同时可以允许多少线程进行访问
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {    //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

下面说一下Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:

public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { }    //获取permits个许可
public void release() { }          //释放一个许可
public void release(int permits) { }    //释放permits个许可

acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。

release()用来释放许可。注意,在释放许可之前,必须先获获得许可。

这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

public boolean tryAcquire() { };    //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false

假若一个工厂有3台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:

import java.util.Random;
import java.util.concurrent.Semaphore;
/**
 * 假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
 *
 */
public class Test {
	
	public static void main(String[] args) throws InterruptedException {
		int N = 8; // 工人数
		Semaphore semaphore = new Semaphore(5); // 机器数目
		
		for (int i = 1; i <= N; i++) {
			int j = i;
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					try {
						System.out.println(System.currentTimeMillis()+".工人" + j + "等待机器...");
						semaphore.acquire();
						long start = System.currentTimeMillis();
						System.out.println(start+".工人" + j +"得到一个机器在生产...");
						Thread.sleep(new Random().nextInt(3000));
						long end = System.currentTimeMillis();
						System.out.println(end+".工人" + j + "释放出机器.用时"+(end-start));
						semaphore.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
			
			Thread.sleep(1);
		}
	}
}

输出:

1524641499785.工人1等待机器...
1524641499786.工人1得到一个机器在生产...
1524641499786.工人2等待机器...
1524641499786.工人2得到一个机器在生产...
1524641499787.工人3等待机器...
1524641499787.工人3得到一个机器在生产...
1524641499788.工人4等待机器...
1524641499789.工人5等待机器...
1524641499790.工人6等待机器...
1524641499791.工人7等待机器...
1524641499792.工人8等待机器...
1524641500793.工人2释放出机器.用时1007
1524641500793.工人4得到一个机器在生产...
1524641500971.工人1释放出机器.用时1185
1524641500971.工人5得到一个机器在生产...
1524641501500.工人4释放出机器.用时707
1524641501500.工人6得到一个机器在生产...
1524641501667.工人3释放出机器.用时1880
1524641501667.工人7得到一个机器在生产...
1524641502187.工人6释放出机器.用时687
1524641502187.工人8得到一个机器在生产...
1524641503211.工人7释放出机器.用时1544
1524641503440.工人5释放出机器.用时2469

1524641504493.工人8释放出机器.用时2306


new Semaphore(3)和new Semaphore(3, true),公平和非公平情况下,线程都是顺序执行的。这让我感觉奇怪,非公平情况下,线程应该是抢占的,也就是执行不一定是按照序号顺序执行,但是测试结果却不是这样,为什么呢?

你可能感兴趣的:(java,并发,多线程)