获得更好并发和线程安全性的场景和解决方案--第三部分 信号量和互斥锁

场景1:web服务通过http请求数据,将请求对象放在内部的队列中,并且有一个单独的工作线程从队列中取请求对象进行处理.比如,一个交易系统创建一个购买订单并放入队列中,这时一个独立的消费线程获取订单信息并且将发送给证券交易所,这是一个典型的生产者消费者场景.

场景2:如果之前的条件未知信号量可以用来创建工作线程,这是因为信号量可以在0许可的情况下创建,并在一系列的释放以后运行.比如,如果你想递归的遍历内部文件夹并利用生成的若干线程将html类型的文件转移到目标文件夹中,然后可以通过调用release方法调用递增许可数量, 单独的处理线程需要在开始处理这些文件之前获取足够的许可才可以进行处理.

场景3:当需要对共享资源进行递增或者递减的引用计数时,递增、递减或者测试的操作必须是线程安全的。比如,一个用来在用户登录时递增或者在用户登出时递减当前活动用户的计数器.

方案:通常情况下,信号量是根据字母顺序代码的某个位置通过持有判断标识来发送消息的实现。在程序设计中,特别是在Unix系统中,信号量是在多个进程竞争相同操作系统资源的时候用来协作或者同步活动的技术。信号量通畅被用于两个目的:共享内存空间和共享文件访问。在java中,信号量是用来对多线程进行协作。

Q:互斥锁和信号量之间的区别是什么?

A:互斥锁:好比厕所的钥匙一样。同一时间只能有一个人拥有并且独占厕所。当一个人使用过后,持有钥匙的人会将钥匙传递给队列中的下一个人。在java中,每一个对象都有一个互斥锁并且只能有一个线程可以持有它。

   信号量:是一串厕所的钥匙。比如,厕所有三把锁,对应的钥匙有三个。在刚开始时,信号量的值被设置为3,随后当有人拿到钥匙进入厕所后值会递减。如果厕所满了,也就是没有钥匙省下的时候,信号量的值是0.现在,如果有一个人使用完厕所离开后,信号量会递增为1(获得了一个钥匙),并且将钥匙传递给了排队的下个人。

这里有使用互斥锁来实现生产者和消费者的例子.

public class ProducerConsumer {
 
 private int count;  //被多个线程共享访问的资源
 
 public synchronized void consume() {
  while (count == 0) {
   try {
    wait();    //等待唤醒
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 
  //如果信号量的值大于0 ,自减
  count--;     //递减1
  System.out.println("Consumed:" + count);
  notify();  //唤醒等待线程恢复运行
 
 }
 
 public synchronized void produce() {
  while(count > 0) {
   try {
    wait();    //wait till notified
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
   
  //如果信号量的值等于0,递增
  count++;
  System.out.println("Produced: " + count);
  notify();   //唤醒等待线程恢复运行
 }
 
 public static void main(String[] args)  {
        //内部类只能访问final类型的变量
  final ProducerConsumer pc = new ProducerConsumer();
 
  //使用匿名内部类创建生成线程
  Runnable producer = new Runnable() {
   @Override
   public void run() {
    for (int i = 0; i < 3; i++) {
     pc.produce(); 
    }
   }
  };
 
   
  //匿名内部类创建消费线程
  Runnable consumer = new Runnable() {
   @Override
   public void run() {
    for (int i = 0; i < 3; i++) {
      pc.consume();
    }
   }
  };
 
  Thread producerThread = new Thread(producer); //主线程中创建新线程
  Thread consumerThread = new Thread(consumer); //主线程中创建新县城
 
  producerThread.start();  // 执行生产线程
  consumerThread.start();  // 执行消费线程
 
 }
}

现在,让我们来看下Java 5中的信号量的例子。你可以使用一个数量的许可创建信号量,并且可以获得和释放许可。

import java.util.concurrent.Semaphore;
 
public class ProducerConsumer2 {
 
 private int count;
  
 /**
  *  调用release方法递增许可
  *  调用acquire方法递减许可
  */
 static Semaphore semCon = new Semaphore(0);
 static Semaphore semProd = new Semaphore(1); //从一个许可开始
 
 public  void consume() {
  try {
   //继续执行前从信号量获取许可
   semCon.acquire();
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 
  count--;
  System.out.println("Consumed:" + count);
  //释放许可,返回给信号量
  semProd.release(); 
 
 }
 
 public void produce() {
  try {
   //执行前获得信号量许可
   semProd.acquire(); 
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  count++;
  System.out.println("Produced: " + count);
  //释放许可,归还给信号量
  semCon.release();   
 }
 
 public static void main(String[] args)  {
 
  final ProducerConsumer2 pc = new ProducerConsumer2();
 
  //匿名内部类生产线程
  Runnable producer = new Runnable() {
   @Override
   public void run() {
    for (int i = 0; i < 3; i++) {
     pc.produce(); 
    }
   }
  };
 
  //匿名内部类消费线程
  Runnable consumer = new Runnable() {
   @Override
   public void run() {
    for (int i = 0; i < 3; i++) {
      pc.consume();
    }
   }
  };
 
  Thread producerThread = new Thread(producer); //主线程创建新线程
  Thread consumerThread = new Thread(consumer); //主线程创建新县城
 
  producerThread.start(); // 启动生产者线程
  consumerThread.start(); // 启动消费者线程
 
 }
}

上面两端代码的输出:

Produced: 1
Consumed:0
Produced: 1
Consumed:0
Produced: 1
Consumed:0

猜你喜欢

转载自clearity.iteye.com/blog/2047811