简介
本文学习信号量Semaphore机制。
Semaphore
- 本质是一个共享锁
- 内部维护一个可用的信号集,获取信号量之前需要先申请获取信号数量;用完之后,则需要释放信号量;如果不释放,那么其他等待线程则一直阻塞直到获取信号量或则被中断为止
- 本人的理解是:互斥锁是同一时间只能一个线程访问,而在这里,是同一时间允许获取到了信号量的线程并发访问,而没有获取到信号量的则必须等待信号量的释放;
- 将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)。
- 同样信号量也分为“公平信号量”和“非公平信号量”,他们的关系就和普通的一致,不过也是包裹了一层“信号量”
下面通过以前的获取苹果的价格的列子来演示信号量的使用
示例1:先来看看上面第3点
public class Client {
public static void main(String[] args) {
final GoodInfo gi = new GoodInfo();
Thread[] read = new Thread[10]; //读线程,读取10次商品信息
for (int i = 0; i < 10; i++) {
read[i] = new Thread(new Runnable() {
@Override
public void run() {
gi.getInfo();
}
});
}
final Thread[] write = new Thread[3]; //写线程,修改3次价格
for (int i = 0; i < 3; i++) {
final int finalI = i;
write[i] = new Thread(new Runnable() {
@Override
public void run() {
gi.setPrice();
}
});
}
for (Thread t : write) {
t.start();
}
for (Thread t : read) {
t.start();
}
}
}
/** 商品信息类 */
class GoodInfo {
private String name = "苹果"; //商品名称
private double price = 10; //商品价格
private final Semaphore sh = new Semaphore(3); //申请3个信号量
/**
* 读取商品信息
*
* @return
*/
public void getInfo() {
try {
sh.acquire(1); //获取1个信号量,如果不够,则当前线程等待
String name = this.name;
double price = this.price;
System.out.println(Thread.currentThread().getName() + "线程获取了商品信息:" + name + ":" + price);
} catch (Exception e) {
e.printStackTrace();
} finally {
sh.release(); //释放信号量
}
}
/**
* 修改商品价格,每次都自增1
*
*/
public void setPrice() {
try {
sh.acquire(1);
this.price = this.price + 1; //目的是:多个线程并发的时候会出现错误数据(数据竞争来验证获取了信号量的线程是并发线程)
System.out.println("--------------" + Thread.currentThread().getName() + "线程修改了商品价格:" + name + ":" + price);
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
} finally {
sh.release(); //如果把这里注释掉,则可以看出,其他等待获取信号量的是获取不到信号量了。因为一单这里的三个线程把可用的信号量占用完之后,其他线程就只能等待可用的信号量了
}
}
}
某一次的运行结果:
--------------Thread-11线程修改了商品价格:苹果:11.0
Thread-5线程获取了商品信息:苹果:10.0
Thread-7线程获取了商品信息:苹果:11.0
Thread-0线程获取了商品信息:苹果:11.0
Thread-1线程获取了商品信息:苹果:11.0
Thread-2线程获取了商品信息:苹果:11.0
Thread-4线程获取了商品信息:苹果:11.0
Thread-3线程获取了商品信息:苹果:11.0
--------------Thread-10线程修改了商品价格:苹果:12.0
Thread-6线程获取了商品信息:苹果:12.0
--------------Thread-12线程修改了商品价格:苹果:13.0
Thread-8线程获取了商品信息:苹果:13.0
Thread-9线程获取了商品信息:苹果:13.0
如果把写价格的释放信号量注释掉,运行结果是
--------------Thread-12线程修改了商品价格:苹果:11.0
Thread-8线程获取了商品信息:苹果:10.0
Thread-0线程获取了商品信息:苹果:11.0
Thread-4线程获取了商品信息:苹果:11.0
Thread-2线程获取了商品信息:苹果:11.0
Thread-3线程获取了商品信息:苹果:11.0
Thread-1线程获取了商品信息:苹果:11.0
Thread-6线程获取了商品信息:苹果:11.0
Thread-5线程获取了商品信息:苹果:11.0
Thread-7线程获取了商品信息:苹果:11.0
--------------Thread-10线程修改了商品价格:苹果:12.0
--------------Thread-11线程修改了商品价格:苹果:13.0
线程没有运行完,没有获取到信号量的线程,则一直等待。造成了死锁。
说明
上面的运行结果可以看出来:
线程11修改了商品结果,但是线程5和线程11 并发的获取了原有的价格10,导致了线程5获取到了错误的数据信息。
示例2:实现二进制信号量
把以上示例的 构造信号量对象的参数置为1
new Semaphore(1); /创建二进制信号量
某一次的运行结果是
--------------Thread-10线程修改了商品价格:苹果:11.0
--------------Thread-11线程修改了商品价格:苹果:12.0
Thread-1线程获取了商品信息:苹果:12.0
Thread-2线程获取了商品信息:苹果:12.0
Thread-5线程获取了商品信息:苹果:12.0
Thread-6线程获取了商品信息:苹果:12.0
Thread-9线程获取了商品信息:苹果:12.0
--------------Thread-12线程修改了商品价格:苹果:13.0
Thread-0线程获取了商品信息:苹果:13.0
Thread-4线程获取了商品信息:苹果:13.0
Thread-3线程获取了商品信息:苹果:13.0
Thread-7线程获取了商品信息:苹果:13.0
Thread-8线程获取了商品信息:苹果:13.0
可以看出来,而互斥锁的效果一致。
而这个类的使用场景。我暂时还没想明白。为什么有这样的机制。至于公平信号量和非公平信号量就不演示了。很简单的。知道互斥锁和信号量的区别是什么就行了
另外的acquire方法
- acquireUninterruptibly
其实差不多就是acquire(),底层还是调用了tryAcquireShared(arg)方法,从此信号量中获取许可,在有可用的许可前将其阻塞。 - tryAcquire
尝试过去信号量,能获取就返回true,不能就返回false;从而避开线程阻塞,在使用公平锁的使用要注意此方法。会破坏公平性