Java 信号量 Semaphore 介绍

 Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。 

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁 ReentrantLock 也可以实现该功能,但实现上要复杂些。 

下面的Demo中申明了一个只有5个许可的Semaphore,而有20个线程要访问这个资源,通过acquire()和release()获取和释放访问许可。

package com.test;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

public class TestSemaphore {

                public static void main(String[] args) {

                // 线程池

                ExecutorService exec = Executors.newCachedThreadPool();

                // 只能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 {

                                                                    // 获取许可

                                                                    semp.acquire();

                                                                    System.out.println("Accessing: " + NO);

                                                                    Thread.sleep((long) (Math.random() * 10000));

                                                                    // 访问完后,释放

                                                                    semp.release();

                                                                    System.out.println("-----------------"+semp.availablePermits());

                                                            } catch (InterruptedException e) {

                                                                    e.printStackTrace();

                                                            }

                                                  }

                                      };

                      exec.execute(run);

             }

             // 退出线程池

             exec.shutdown();

       }

执行结果如下:

Accessing: 0

Accessing: 1

Accessing: 3

Accessing: 4

Accessing: 2

-----------------0

Accessing: 6

-----------------1

Accessing: 7

-----------------1

Accessing: 8

-----------------1

Accessing: 10

-----------------1

Accessing: 9

-----------------1

Accessing: 5

-----------------1

Accessing: 12

-----------------1

Accessing: 11

-----------------1

Accessing: 13

-----------------1

Accessing: 14

-----------------1

Accessing: 15

-----------------1

Accessing: 16

-----------------1

Accessing: 17

-----------------1

Accessing: 18

-----------------1

Accessing: 19




 一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。

相关方法:

acquire

public void acquire()
             throws InterruptedException
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被 中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。

如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态:

  • 某些其他线程调用此信号量的 release() 方法,并且当前线程是下一个要被分配许可的线程;或者
  • 其他某些线程中断当前线程。

如果当前线程:

  • 被此方法将其已中断状态设置为 on ;或者
  • 在等待许可时被中断

则抛出 InterruptedException,并且清除当前线程的已中断状态。 

抛出:
InterruptedException - 如果当前线程被中断

release

public void release()
释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。

不要求释放许可的线程必须通过调用 acquire() 来获取许可。通过应用程序中的编程约定来建立信号量的正确用法。

 

相关例子:


Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问: 

 class Pool {
   private static final int MAX_AVAILABLE = 100;
   private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

   public Object getItem() throws InterruptedException {
     available.acquire();
     return getNextAvailableItem();
   }

   public void putItem(Object x) {
     if (markAsUnused(x))
       available.release();
   }

   // Not a particularly efficient data structure; just for demo

   protected Object[] items = ... whatever kinds of items being managed
   protected boolean[] used = new boolean[MAX_AVAILABLE];

   protected synchronized Object getNextAvailableItem() {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (!used[i]) {
          used[i] = true;
          return items[i];
       }
     }
     return null; // not reached
   }

   protected synchronized boolean markAsUnused(Object item) {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (item == items[i]) {
          if (used[i]) {
            used[i] = false;
            return true;
          } else
            return false;
       }
     }
     return false;
   }

 }
 获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。 
 
 
 
 
 
 
 
 
 
 




package cn.heima.test;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class MySemaphoreDemo {
  /**信号量好比一家餐馆有n个座位,假如多个线程好比一群人冲进餐馆,
   * 当然是先占到座位的先开始有饭吃(站着吃饭的不考虑)
   * 座位有限没占到座位的之后等待(阻塞),等别人吃完了离开(释放信号)
   * 一有空位,等会的人再去抢,抢上就看运气了
   */
  public static void show(Object o)
  {
    System.out.println(o);
  }
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    //创建一个固定数量为10的线程池
        ExecutorService services=Executors.newFixedThreadPool(10);
        //创建一个信号量为3的Semaphore对象
        final Semaphore sp=new Semaphore(3);
        for(int i=0;i<10;i++)
        	services.execute(new Runnable()
        	{
        		public void run()
        		{
        			//判断还有信号量可以获取吗
        			if(sp.availablePermits()<=0)
        			   show("没有座位了"+Thread.currentThread().getName()+"请等候");

          			try {
              //获得信号,则执行线程,信号没了,暂时阻塞
          				sp.acquire();
            } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
            show(Thread.currentThread().getName()+"进门,占到座位!开始吃饭");
            try {
              Thread.sleep(new Random().nextInt(5000));
            } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
            show(Thread.currentThread().getName()+"吃完了,走了,又有空座位 了");
            //线程运行结束,信号释放
            sp.release();
        		}
        	});
  }

}
运行结果如下:

pool-1-thread-1进门,占到座位!开始吃饭
pool-1-thread-2进门,占到座位!开始吃饭
pool-1-thread-3进门,占到座位!开始吃饭
没有座位了pool-1-thread-5请等候
没有座位了pool-1-thread-4请等候
没有座位了pool-1-thread-6请等候
没有座位了pool-1-thread-8请等候
没有座位了pool-1-thread-10请等候
没有座位了pool-1-thread-7请等候
没有座位了pool-1-thread-9请等候
pool-1-thread-1吃完了,走了,又有空座位 了
pool-1-thread-5进门,占到座位!开始吃饭
pool-1-thread-3吃完了,走了,又有空座位 了
pool-1-thread-4进门,占到座位!开始吃饭
pool-1-thread-4吃完了,走了,又有空座位 了
pool-1-thread-6进门,占到座位!开始吃饭
pool-1-thread-2吃完了,走了,又有空座位 了
pool-1-thread-8进门,占到座位!开始吃饭
pool-1-thread-5吃完了,走了,又有空座位 了
pool-1-thread-10进门,占到座位!开始吃饭
pool-1-thread-10吃完了,走了,又有空座位 了
pool-1-thread-7进门,占到座位!开始吃饭
pool-1-thread-7吃完了,走了,又有空座位 了
pool-1-thread-9进门,占到座位!开始吃饭
pool-1-thread-6吃完了,走了,又有空座位 了
pool-1-thread-8吃完了,走了,又有空座位 了
pool-1-thread-9吃完了,走了,又有空座位 了



你可能感兴趣的:(java,Semaphore,信号量,介)