代码改变世界

多线程信号量 Semaphore使用

2014-03-07 15:53  youxin  阅读(1505)  评论(0编辑  收藏  举报


  对信号量只能实施三种操作: 
  1. 初始化(initialize),也叫做建立(create) 
  2. 等信号(wait),也可叫做挂起(pend) 
  3. 给信号(signal)或发信号(post)

 

分类:


   整型信号量(integer semaphore):信号量是整数 
  记录型信号量(record semaphore):每个信号量s除一个整数值s.value(计数)外,还有一个进程等待队列s.L,其中是阻塞在该信号量的各个进程的标识 
  二进制信号量(binary semaphore):只允许信号量取0或1值 
  每个信号量至少须记录两个信息:信号量的值和等待该信号量的进程队列。它的类型定义如下:(用类PASCAL语言表述) 
  semaphore = record 
  value: integer; 
  queue: ^PCB; 
  end; 
  其中PCB是 进程控制块,是 操作系统为每个进程建立的 数据结构。 
  s.value>=0时,s.queue为空; 
  s.value<0时,s.value的 绝对值为s.queue中等待进程的个数;

 

  JDK的并发包就给我提供了一个类似的信号量类——Semaphore 。

生产者-消费者问题 

 

import java.util.concurrent.Semaphore;

class Signs{
      static Semaphore empty=new Semaphore(10); //信号量:记录仓库空的位置
    static Semaphore full=new Semaphore(0);   //信号量:记录仓库满的位置
    static Semaphore mutex=new Semaphore(1);  //临界区互斥访问信号量(二进制信号量),相当于互斥锁。
}
class Producer implements Runnable{
    
      public void run(){
           try {
                  while(true){
                         Signs.empty.acquire(); //递减仓库空信号量
          Signs.mutex.acquire(); //进入临界区
          System.out.println("生成一个产品放入仓库");
             Signs.mutex.release(); //离开临界区
          Signs.full.release();  //递增仓库满信号量
          Thread.currentThread().sleep(100);
      }
           } catch (InterruptedException e) {
        e.printStackTrace();
           }
      }
}
class Consumer implements Runnable{
       public void run(){
            try {
                 while(true){
           Signs.full.acquire(); //递减仓库满信号量
         Signs.mutex.acquire();
           System.out.println("从仓库拿出一个产品消费");
           Signs.mutex.release();
           Signs.empty.release(); //递增仓库空信号量
         Thread.currentThread().sleep(1000);
      }
            } catch (InterruptedException e) {
       e.printStackTrace();
            }
       }
    
}
public class Test{

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
}

进一步可参考:http://blog.csdn.net/ljsspace/article/details/6702093

发现上面的程序有些问题,原因在于mutex,empty,full这几个变量虽然是static,但在多线程中任然会创建几个实例,起不到效果。后来一想,其实不会,为什么,因为

static Semaphore empty=new Semaphore(10);
是急切实例化的,jvm加载这个类的时候就创建了对象,保证了所有的线程中此对象的唯一性,参考单例的实现方式:http://www.cnblogs.com/youxin/archive/2012/11/26/2788899.html

  哲学家进餐问题

   在1965年,Dijkstra提出并解决了一个他称之为哲学家进餐的同步问题。从那时起,每个发明新的同步原语的人都希望通过解决哲学家进餐间题来展示其同步原语的精妙之处。这个问题可以简单地描述:五个哲学家围坐在一张圆桌周围,每个哲学家面前都有一碟通心面,由于面条很滑,所以要两把叉子才能夹住。相邻两个碟子之间有一把叉子。
      哲学家的生活包括两种活动:即吃饭和思考。当一个哲学家觉得饿时,他就试图去取他左边和右边的叉子。如果成功地获得两把叉子,他就开始吃饭,吃完以后放下叉子继续思考。

问题描述:
  一个房间内有5个哲学家,他们的生活就是思考和进食。房间里有一张圆桌,中间放着一盘通心粉(假定通心粉无限多)。桌子周围放有五把椅子,分别属于五位哲学家每两位哲学家之间有一把叉子,哲学家进食时必须同时使用左右两把叉子。
解答:
  进程:philosopher - 哲学家 
  共有的数据结构&过程:
     state: array [0..4] of (think,hungry,eat); 
     ph: array [0..4] of semaphore; 
       — 每个哲学家有一个信号量,初值为0 
     mutex: semaphore; 
       — mutex保护临界区,初值=1
     procedure test(i:0..4); 
     { 
       if ((state[i]=hungry) and (state[(i+1)mod 5]<>eating)
       and (state[(i-1)mod 5]<>eating))
       { state[i]=eating; 
          V(ph[i]); 
       }
     } 

  philosopher(i:0..4): 
  { 
     while (true) 
     { 
       think();
       p(mutex); 
       state[i]=hungry; 
       test(i); 
       v(mutex); 
       p(ph[i]); 
       eat(); 
       p(mutex); 
       state[i]=think; 
       test((i-1) mod 5); 
       test((i+1) mod 5); 
       v(mutex); 
     } 
  } 

 

更多:

http://hxraid.iteye.com/blog/739265

 

第一类读者写者问题:

问题描述:
  一些读者和一些写者对同一个黑板进行读写。多个读者可同时读黑板,但一个时刻只能有一个写者,读者写者不能同时使用黑板。对使用黑板优先级的不同规定使读者-写者问题又可分为几类。第一类问题规定读者优先级较高,仅当无读者时允许写者使用黑板。
解答:
  进程:writer - 写者进程,reader - 读者进程 
  共有的数据结构:
     read_account:integer; 
     r_w,mutex: semaphore; 
       — r_w控制谁使用黑板,mutex保护临界区,初值都为1
  reader - (读者进程): 
  { 
     while (true) 
     { 
       p(mutex);
       read_account++; 
       if(read_account=1) p(r_w); 
       v(mutex); 
       read(); 
       p(mutex); 
       read_account--; 
       if(read_account=0) v(r_w);; 
       v(mutex); 
     } 
  } 

  writer - (写者进程): 
  { 
     while (true) 
     { 
       p(mutex); 
        write(); 
        v(mutex); 
     } 
   } 

读者-写者问题 
 
读者一写者问题(Courtois et al., 1971)为数据库访问建立了一个模型。例如,设想一个飞机定票系统,其中有许多竞争的进程试图读写其中的数据。多个进程同时读是可以接受的,但如果一个进程正在写数据库、则所有的其他进程都不能访问数据库,即使读操作也不行。

import java.util.concurrent.Semaphore;

class Sign{
    static Semaphore db=new Semaphore(1); //信号量:控制对数据库的访问
    static Semaphore mutex=new Semaphore(1); //信号量:控制对临界区的访问
    static int rc=0; //记录正在读或者想要读的进程数
}
class Reader implements Runnable{
    
    public void run(){
        try {
            //互斥对rc的操作
            Sign.mutex.acquire();
            Sign.rc++;  //又多了一个读线程
            if(Sign.rc==1) Sign.db.acquire(); //如果是第一个读进程开始读取DB,则请求一个许可,使得写进程无法操作

DB
            Sign.mutex.release();
                
            //无临界区控制,多个读线程都可以操作DB
            System.out.println("[R]   "+Thread.currentThread().getName()+": read data....");
            Thread.sleep(100);
            
            //互斥对rc的操作
            Sign.mutex.acquire();
            Sign.rc--;
            if(Sign.rc==0) Sign.db.release(); //如果最后一个读进程读完了,则释放许可,让写进程有机会操作DB
            Sign.mutex.release();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Writer implements Runnable{
    
    public void run(){
        try {
            //与读操作互斥访问DB
            Sign.db.acquire();
            System.out.println("[W]   "+Thread.currentThread().getName()+": write data....");
            Thread.sleep(100);
            Sign.db.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
    }
}
public class Test {

    public static void main(String[] args){
        new Thread(new Reader()).start();
        new Thread(new Reader()).start();
        new Thread(new Writer()).start();
    }
}