前言
概念
临界资源(临界区) | 指一次只能允许一个进程使用的共享资源称为临界资源 |
同步 | 指为完成某种任务而建立的两个和多个进程,这些进程在合作的过程中需要协调工作次序进行有序的访问而出现等待所产生的制约关系 |
互斥 | 指两个或多个进程访问临界资源时只能一个进程访问,其他进程等待的一种相互制约的关系 |
信号量 | 本身是一个计数器,使用P,V两个操作来实现计数的减与加,当计数不大于0时,则进程进入睡眠状态,它用于为多个进程提供共享数据对象的访问。 |
互斥量 | 如果信号量只存在两个状态,那就不需要计数了,可以简化为加锁与解锁两个功能,这就是互斥量 |
PV操作 | p操作(wait)申请一个单位资源,进程进入;v操作(signal)释放一个单位资源,进程出来 |
引入
锁机制只能判断临界资源是否被占用,所以他解决了互斥问题,但是他不能确定前面的进程是否完成,所以他不能用于同步问题中
有哪些同步问题(如生产者消费者模型)
生产者与消费者进程(线程)对缓冲区的访问是互斥关系,而生产者与消费者本身又存在同步关系,即必须生成之后才能消费
生产者和消费者伪代码实现
semaphore mutex=1; //互斥量
semaphore full=0; //满缓冲区单元
semaphore empty=N; //空闲缓冲区单元
prodecer()
{
while(1)
{
P(empty);
P(mutex);
add_source++;
V(mutex);
V(full);
}
}
consumer()
{
while(1)
{
P(full);
P(mutex);
add_source--;
V(mutex);
V(empty);
}
}
Semaphore信号量
java 1.5 后 引入Semaphore类
"计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时 执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。"
"Semaphore中管理着一组虚拟的许可(permit),许可的初始数量可通过构造函数来指定。在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用以后释放许可。如果没有许可,那么acquire将阻塞直到有许可(或者直到被中断或者操作超时)。release方法将返回一个许可给信号量。计算信号量的一种简化形式是二值信号量,即初始值为1的Semaphore。二值信号量可以用做互斥体(mutex),并具备不可重入的加锁语义:谁拥有这个唯一的许可,谁就拥有了互斥锁。"
"概念上讲,一个信号量管理许多的许可证(permit)。为了通过信号量,线程通过调用 acquire 请求许可。其实没有实际的许可对象, 信号量仅维护一个计数。许可的数目是固定 的,由此限制了通过的线程数量。其他线程可以通过调用 release 释放许可。而且,许可不是二 必须由获取它的线程释放。事实上,任何线程都可以释放任意数目的许可,这可能会增加许可数目以至于超出初始数目。"
Semaphore的主要方法摘要:
- void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
- void release():释放一个许可,将其返回给信号量。
- int availablePermits():返回此信号量中当前可用的许可数。
- boolean hasQueuedThreads():查询是否有线程正在等待获取。
使用
Semaphore可以用于实现资源池,例如数据库连接池。我们可以构造一个固定长度的资源池,当池为空时,请求资源将会失败,但你真正希望看到的行为是阻塞而不是失败,并且当池非空时解除阻塞。如果将Semaphore的计数值初始化为池的大小,并在从池中获取一个资源 之前首先调用acquire方法获取一个许可,在将资源返回给池之后调用release释放许可,那么 acquire将一直阻塞直到资源池不为空。(在构造阻塞对象池时,一种更简单的方法是使用BlockingQueue阻塞队列来保存池的资源。)
1.使用Semaphore将任何一种容器变成有界阻塞容器
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());//同步容器
sem = new Semaphore(bound);//初始化信号量
}
public boolean add(T o) throws InterruptedException {
sem.acquire();//获得许可
boolean wasAdded = false;
try{
wasAdded = set.add(o);
return wasAdded;
}finally{
if(!wasAdded){
sem.release();
}
}
}
public boolean remove(Object o) {
boolean wasRemoved = set.remove(o);
if(wasRemoved){
sem.release();//释放许可
}
return wasRemoved;
}
}
2.创建了一个无限制大小的缓存线程池,并模拟20个客户端进行访问,通过Semaphore控制最高并发量为5个
package com.java.semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import org.junit.Test;
public class SemaphoreTest {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
//设置信号量同时执行的线程数是5 , 通过信号量机制控制最大线程并发量为5
final Semaphore semp = new Semaphore(5); //参数:许可证数量
// 模拟20个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
//使用acquire()获取锁
semp.acquire();
//System.out.println("Accessing: " + NO);
System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (5-semp.availablePermits()) + "个并发");
//睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
} finally {
//使用完成释放锁
semp.release();
System.out.println("线程" + Thread.currentThread().getName() + "离开,当前已有" + (5-semp.availablePermits()) + "个并发");
}
}
};
exec.execute(run); //执行
}
// 退出线程池
exec.shutdown();
}
}
3.使用Semaphore信号量机制来模拟生产者消费者模型
public class Test {
int count = 0;
final Semaphore put = new Semaphore(5);// 初始令牌个数
final Semaphore get = new Semaphore(0);
final Semaphore mutex = new Semaphore(1);
public static void main(String[] args) {
Test t = new Test();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Producer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
try {
put.acquire();// 注意顺序
mutex.acquire();
count++;
System.out.println("生产者" + Thread.currentThread().getName()
+ "已生产完成,商品数量:" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
mutex.release();
get.release();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
get.acquire();// 注意顺序
mutex.acquire();
count--;
System.out.println("消费者" + Thread.currentThread().getName()
+ "已消费,剩余商品数量:" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
mutex.release();
put.release();
}
}
}
}
}