关于Semaphore举例
以一个停车场运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
信号量的特性如下:
信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。 当一个线程调用Wait(等待)操作时,它要么通过然后将信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源。
在java中,还可以设置该信号量是否采用公平模式,如果以公平方式执行,则线程将会按到达的顺序(FIFO)执行,如果是非公平,则可以后请求的有可能排在队列的头部。
JDK中定义如下:
Semaphore(int permits, boolean fair)
创建具有给定的许可数和给定的公平设置的Semaphore。
Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java并发库Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。
与Semaphore具有相同作用的调度工具有CountDownLatch、CyclicBarrier
一.多线程资源分配
1.一般模式
package com.tianwt.app;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreThreadPool {
public static int numbers = 20;
public static void main(String[] args) {
// 允许2个线程同时访问
final Semaphore semaphore = new Semaphore(2);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
final int index = i+1;
executorService.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();
// 这里可能是业务代码
numbers = numbers-1;
System.out.println("("+index+")."+"线程:" + Thread.currentThread().getName() + ",numbers=" + numbers);
TimeUnit.SECONDS.sleep(1);
semaphore.release();
System.out.println("允许TASK个数:" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
执行结果如下:
(1).线程:pool-1-thread-1,numbers=18
(2).线程:pool-1-thread-2,numbers=18
(4).线程:pool-1-thread-4,numbers=16
(3).线程:pool-1-thread-3,numbers=16
允许TASK个数:0
允许TASK个数:0
允许TASK个数:2
允许TASK个数:0
(5).线程:pool-1-thread-5,numbers=14
(6).线程:pool-1-thread-6,numbers=14
(7).线程:pool-1-thread-7,numbers=13
允许TASK个数:0
(10).线程:pool-1-thread-10,numbers=12
允许TASK个数:0
允许TASK个数:1
允许TASK个数:1
(11).线程:pool-1-thread-11,numbers=10
(9).线程:pool-1-thread-9,numbers=10
允许TASK个数:0
(8).线程:pool-1-thread-8,numbers=8
允许TASK个数:1
(12).线程:pool-1-thread-12,numbers=9
允许TASK个数:2
允许TASK个数:2
(14).线程:pool-1-thread-14,numbers=6
(13).线程:pool-1-thread-13,numbers=6
允许TASK个数:2
允许TASK个数:0
(16).线程:pool-1-thread-16,numbers=4
(15).线程:pool-1-thread-15,numbers=4
允许TASK个数:2
允许TASK个数:2
(18).线程:pool-1-thread-18,numbers=2
(17).线程:pool-1-thread-17,numbers=2
(19).线程:pool-1-thread-19,numbers=1
(20).线程:pool-1-thread-20,numbers=0
允许TASK个数:0
允许TASK个数:0
允许TASK个数:1
允许TASK个数:2
从执行结果来看,CacheThreadPool连续创建了20个线程,同样也印证了Semaphore资源分配功能。遗憾的是的Semaphore使用公平模式初始化时,并未完全进行FIFO模式排队。另外一点,从numbers我们知道,Semaphore只是用来实现资源分配的,并不会资源同步。
final Semaphore semaphore = new Semaphore(2,true);
运行结果如下:
2.同步模式
(2).线程:pool-1-thread-2,numbers=18
(1).线程:pool-1-thread-1,numbers=18
允许TASK个数:0
(3).线程:pool-1-thread-3,numbers=16
允许TASK个数:0
(4).线程:pool-1-thread-4,numbers=17
允许TASK个数:2
允许TASK个数:0
(5).线程:pool-1-thread-5,numbers=14
(6).线程:pool-1-thread-6,numbers=14
允许TASK个数:0
(9).线程:pool-1-thread-9,numbers=12
(7).线程:pool-1-thread-7,numbers=13
允许TASK个数:0
(11).线程:pool-1-thread-11,numbers=10
允许TASK个数:0
允许TASK个数:0
(10).线程:pool-1-thread-10,numbers=10
(8).线程:pool-1-thread-8,numbers=8
允许TASK个数:2
允许TASK个数:0
(12).线程:pool-1-thread-12,numbers=8
允许TASK个数:2
(14).线程:pool-1-thread-14,numbers=6
允许TASK个数:2
(13).线程:pool-1-thread-13,numbers=6
允许TASK个数:0
允许TASK个数:0
(16).线程:pool-1-thread-16,numbers=4
(15).线程:pool-1-thread-15,numbers=5
允许TASK个数:0
(17).线程:pool-1-thread-17,numbers=3
(18).线程:pool-1-thread-18,numbers=2
允许TASK个数:0
允许TASK个数:1
允许TASK个数:2
(20).线程:pool-1-thread-20,numbers=0
(19).线程:pool-1-thread-19,numbers=1
允许TASK个数:2
允许TASK个数:2
我们看到,pool-1-thread-8和pool-1-thread-7相距很远。
2.同步模式
如何解决3个不足呢?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreThreadPool {
public static int numbers = 20;
public static void main(String[] args) {
// 允许2个线程同时访问
final Semaphore semaphore = new Semaphore(2,true);
final ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 20; i++) {
final int index = i+1;
executorService.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();
// 这里可能是业务代码
synchronized (executorService) {
numbers = numbers-1;
System.out.println("("+index+")."+"线程:" + Thread.currentThread().getName() + ",numbers=" + numbers);
TimeUnit.SECONDS.sleep(1);
}
semaphore.release();
System.out.println("允许TASK个数:" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
这样就可以解决线程池过多,FIFO问题以及同步问题。
二.线程等待
public static boolean isLocked = true;
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(0);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
System.out.println(Thread.currentThread().getName()+":start");
/**
* (●'◡'●)
* 注意,如果是重要的变量值修改,必须在semaphore调用release之前,否则有可能造成未知问题
* (●'◡'●)
*/
isLocked = true;
semaphore.release(1);
System.out.println(Thread.currentThread().getName()+":end");
}
}
});
t.start();
semaphore.acquire(); //因为初始信号量为0,因此这里阻塞等待
System.out.println("主线程执行!");
semaphore.release();
}
之前在iOS开发中遇到变量值修改不同不得问题,在这里提醒开发者,如果是重要的变量值修改,必须在semaphore调用release之前,否则有可能造成未知问题。