这里我又将引入线程里又一个重要的概念:信号量。
什么是信号量了?这个问题似乎很复杂,我现在获得的理解应该是最简单的理解,下面是我从网上资料总结出来的结论:
多个线程访问某一个资源,例如数据库的连接。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程?一种控制访问一组资源的方法(除了简单地上锁之外),就是使用众所周知的信号量计数 (counting semaphore)。Java多线程信号量计数将一组可获得资源的管理封装起来。信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器。例如我们可以将一个信号量初始化为可获得的数据库连接个数。一旦某个线程获得了Java多线程信号量,可获得的数据库连接数减一。线程消耗完资源并释放该资源时,计数器就会加一。当信号量控制的所有资源都已被占用时,若有线程试图访问此信号量,则会进入阻塞状态,直到有可用资源被释放。Java多线程信号量最常见的用法是解决“消费者-生产者问题”。当一个线程进行工作时,若另外一个线程访问同一共享变量,就可能产生此问题。消费者线程只能在生产者线程完成生产后才能够访问数据。使用信号量来解决这个问题,就需要创建一个初始化为零的信号量,从而让消费者线程访问此信号量时发生阻塞。每当完成单位工作时,生产者线程就会向该信号量发信号(释放资源)。
对于信号量我们可以简单的这么来理解它,信号量就是两个线程间通信的标志对象。信号量为0,则表明信号量监控的资源是可用的,不为零则信号量监控的资源是不可用的,线程们都要等待了,当资源可用的时候,线程会增加信号量的值,然后继续执行并使用这个监控资源,而信号量这种增加值和减少值的操作是不能被中断的,很保险,所以信号量能够保证两个线程同时访问同一个资源的时候不产生冲突。下面是信号量概念的简化版:
package cn.com.sxia;
public class Semaphore implements Invariant {
private volatile int semaphore = 0;
public void acquire(){
++semaphore;
}
public boolean available(){
return semaphore == 0;
}
public void release(){
--semaphore;
}
@Override
public InvariantState invariant() {
int val = semaphore;
if (val == 0 || val == 1){
return new InvariantOK();
}else{
return new InvariantFailure(new Integer(val));
}
}
}
这个代码里包括三个方法,既然线程在获取资源的时候要检查可用性,我们让调用该类对象,在逻辑上使得semaphore的值都不会是0或1,下面是我写的测试代码了:
package cn.com.sxia;
public class SemaphoreTester extends Thread {
private volatile Semaphore semaphore;
public SemaphoreTester(Semaphore semaphore){
this.semaphore = semaphore;
setDaemon(true);
start();
}
public void run(){
while(true){
if (semaphore.available()){
yield();
semaphore.acquire();
yield();
semaphore.release();
yield();
}
}
}
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore();
new SemaphoreTester(semaphore);
new SemaphoreTester(semaphore);
new InvariantWatcher(semaphore).join();
}
}
大家可以看到run方法里的内容保证了semaphore值都是在0或1来进行,但是我们运行这个main函数总会有报错的时候,例如:
Invariant violated: -1
程序报错退出了,多个线程访问同一个资源会造成数据的错误,这是我们写多线程程序最大的风险所在。