Semaphore通常用于限制可以访问某些资源(物理或逻辑的)的线程数目,我们可以自己设定最大访问量。它有两个很常用的方法是acquire()和release(),分别是获得许可和释放许可。 
  官方JDK上面对Semaphore的解释是这样子的 :

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

  我的解释是这样子的:

Semaphore相当于一个厕所,我在造的时候可以想造几个坑就造几个坑,假如现在我就造了3个坑,现在有10个人想要来上厕所,那么每次就只能3个人上,谁最先抢到谁就进去,出来了一个人后,第4个人才能进去,这个就限制了上厕所的人数了,就这个道理。每个人上厕所之前都先acquire()一下,如果有坑,就可以进入,没有就被阻塞,在外面等;上完厕所后,会release()一下,释放一个坑出来,以保证下一个人acquire()的时候有坑。

  我觉得我的解释比官方的要好……

1. Semaphore基本使用

  Semaphore在限制资源访问量的问题上用处很大,比如限制一个文件的并发访问次数等,它的原理很好理解。下面写一个Semaphore的示例代码:

public class SemaphoreTest {

    public static void main(String[] args) {

        ExecutorService service = Executors.newCachedThreadPool();//使用并发库,创建缓存的线程池
final Semaphore sp = new Semaphore(3);//创建一个Semaphore信号量,并设置最大并发数为3 //availablePermits() //用来获取当前可用的访问次数
system.out.println("初始化:当前有" + (3 - sp.availablePermits() + "个并发")); //创建10个任务,上面的缓存线程池就会创建10个对应的线程去执行
for (int index = 0; index < 10; index++) {
final int NO = index; //记录第几个任务
Runnable run = new Runnable() { //具体任务
public void run() {
try {
sp.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName()
+ "获取许可" + "("+NO+")," + "剩余:" + sp.availablePermits());
Thread.sleep(1000);
// 访问完后记得释放 ,否则在控制台只能打印3条记录,之后线程一直阻塞
sp.release(); //释放许可
System.out.println(Thread.currentThread().getName()
+ "释放许可" + "("+NO+")," + "剩余:" + sp.availablePermits());
} catch (InterruptedException e) {
}
}
};
service.execute(run); //执行任务
}
service.shutdown(); //关闭线程池
}
}

  代码结构很容易理解,10个任务,每次最多3个线程去执行任务,其他线程被阻塞。可以通过打印信息来看线程的执行情况:

初始化:当前有0个并发 
pool-1-thread-1获取许可(0),剩余:1 
pool-1-thread-3获取许可(2),剩余:0 
pool-1-thread-2获取许可(1),剩余:1 
pool-1-thread-1释放许可(0),剩余:3 
pool-1-thread-4获取许可(3),剩余:1 
pool-1-thread-5获取许可(4),剩余:1 
pool-1-thread-2释放许可(1),剩余:3 
pool-1-thread-3释放许可(2),剩余:3 
pool-1-thread-6获取许可(5),剩余:0 
pool-1-thread-4释放许可(3),剩余:2 
pool-1-thread-9获取许可(8),剩余:0 
pool-1-thread-5释放许可(4),剩余:2 
pool-1-thread-6释放许可(5),剩余:2 
pool-1-thread-8获取许可(7),剩余:0 
pool-1-thread-7获取许可(6),剩余:2 
pool-1-thread-8释放许可(7),剩余:2 
pool-1-thread-10获取许可(9),剩余:2 
pool-1-thread-7释放许可(6),剩余:2 
pool-1-thread-9释放许可(8),剩余:2 
pool-1-thread-10释放许可(9),剩余:3

  从结果中看,前三个为什么剩余的不是3,2,1呢?包括下面,每次释放的时候剩余的量好像也不对,其实是对的,只不过线程运行太快,前三个是这样子的:因为最大访问量是3,所以前三个在打印语句之前都执行完了aquire()方法了,或者有部分执行了,从上面的结果来看,线程1是第一个进去的,线程2第二个进去,然后线程1和2开始打印,所以只剩1个了,接下来线程3进来了,打印只剩0个了。后面释放的时候也是,打印前可能有不止一个释放了。

2. Semaphore同步问题

  我从网上查了一下,有些人说Semaphore实现了同步功能,我觉得不对,因为我自己写了个测试代码试了,并不会自己解决并发问题,如果多个线程操作同一个数据,还是需要自己同步一下的。然后我查了一下官方JDK文档(要永远相信官方的文档),它里面是这样说的:

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

  这段官方的解释就很明确了,然后我就明白了网上有些人说的实现了同步的意思是信号量本身封装所需的同步,也就是说我拿到了一个,别人就无法拿到了,我释放了别人才能拿到(就跟我举的厕所的坑一样),但是我拿到了之后去操作公共数据的时候,针对这个数据操作的同步Semaphore就不管了,这就需要我们自己去同步了。下面写一个同步的测试代码:

public class SemaphoreTest2 {

    private static int data = http://blog.csdn.net/eson_15/article/details/0;

    public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
System.out.println("初始化:当前有" + (3 - sp.availablePermits() + "个并发")); // 10个任务
for (int index = 0; index < 10; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
sp.acquire();
System.out.println(Thread.currentThread().getName()
+ "获取许可" + "(" + NO + ")," + "剩余:" + sp.availablePermits());
//实现同步
synchronized(SemaphoreTest2.class) {
System.out.println(Thread.currentThread().getName()
+ "执行data自增前:data="http://blog.csdn.net/eson_15/article/details/ + data);
data++;
System.out.println(Thread.currentThread().getName()
+ "执行data自增后:data="http://blog.csdn.net/eson_15/article/details/ + data);
} sp.release();
System.out.println(Thread.currentThread().getName()
+ "释放许可" + "(" + NO + ")," + "剩余:" + sp.availablePermits());
} catch (InterruptedException e) {
}
}
};
service.execute(run);
}
service.shutdown();
}
}

  看一下运行结果(部分):

初始化:当前有0个并发 
pool-1-thread-2获取许可(1),剩余:0 
pool-1-thread-2执行data自增前:data=http://blog.csdn.net/eson_15/article/details/0 
pool-1-thread-3获取许可(2),剩余:0 
pool-1-thread-1获取许可(0),剩余:0 
pool-1-thread-2执行data自增后:data=http://blog.csdn.net/eson_15/article/details/1 
pool-1-thread-3执行data自增前:data=http://blog.csdn.net/eson_15/article/details/1 
pool-1-thread-3执行data自增后:data=http://blog.csdn.net/eson_15/article/details/2 
pool-1-thread-1执行data自增前:data=http://blog.csdn.net/eson_15/article/details/2 
pool-1-thread-7获取许可(6),剩余:1 
pool-1-thread-3释放许可(2),剩余:2 
pool-1-thread-1执行data自增后:data=http://blog.csdn.net/eson_15/article/details/3

  从结果中可以看出,每个线程在操作数据的前后,是不会受其他线程的影响的,但是其他线程可以获取许可,获取许可了之后就被阻塞在外面,等待当前线程操作完data才能去操作。当然也可以在当前线程操作data的时候,其他线程释放许可,因为这完全不冲突。 
  那如果把上面同步代码块去掉,再试试看会成什么乱七八糟的结果(部分):

初始化:当前有0个并发 
pool-1-thread-3获取许可(2),剩余:0 
pool-1-thread-2获取许可(1),剩余:0 
pool-1-thread-3执行data自增前:data=http://blog.csdn.net/eson_15/article/details/0 
pool-1-thread-2执行data自增前:data=http://blog.csdn.net/eson_15/article/details/0 
pool-1-thread-1获取许可(0),剩余:0 
pool-1-thread-3执行data自增后:data=http://blog.csdn.net/eson_15/article/details/1 
pool-1-thread-2执行data自增后:data=http://blog.csdn.net/eson_15/article/details/2 
pool-1-thread-7获取许可(6),剩余:0 
pool-1-thread-1执行data自增前:data=http://blog.csdn.net/eson_15/article/details/2 
pool-1-thread-8获取许可(7),剩余:0 
pool-1-thread-7执行data自增前:data=http://blog.csdn.net/eson_15/article/details/2 
pool-1-thread-2释放许可(1),剩余:1 
pool-1-thread-7执行data自增后:data=http://blog.csdn.net/eson_15/article/details/4

  从结果中看,已经很明显了,线程2和3都进去了,然后初始data都是0,线程3自增了一下,打印出1是没问题的,但是线程2呢?也自增了一下,却打印出了2。也就是说,线程2在操作数据的前后,数据已经被线程3修改过了,再一次证明Semaphere并没有实现对共有数据的同步,在操作公共数据的时候,需要我们自己实现。 
  Semaphere中如果设置信号量为1的话,那就说明每次只能一个线程去操作任务,那这样的话也就不存在线程安全问题了,所以如果设置信号量为1的话,就可以去掉那个synchronized,不过效率就不行了。 
  Semaphere的使用就总结这么多吧! 
  

  相关阅读:http://blog.csdn.net/column/details/bingfa.html


—–乐于分享,共同进步! 
—–更多文章请看:http://blog.csdn.net/eson_15

【java并发】线程同步工具Semaphore的使用的更多相关文章

  1. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

  2. 线程同步工具 Semaphore类使用案例

    参考博文 : 线程同步工具(一) 线程同步工具(二)控制并发访问多个资源 并发工具类(三)控制并发线程数的Semaphore 使用Semaphore模拟互斥锁 当一个线程想要访问某个共享资源,首先,它 ...

  3. 线程同步工具 Semaphore类的基础使用

    推荐好文: 线程同步工具(一) 线程同步工具(二)控制并发访问多个资源 并发工具类(三)控制并发线程数的Semaphore 简介 Semaphore是基于计数的信号量,可以用来控制同时访问特定资源的线 ...

  4. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

  5. Java核心知识点学习----线程同步工具类,CyclicBarrier学习

    线程同步工具类,CyclicBarrier日常开发较少涉及,这里只举一个例子,以做备注.N个人一块出去玩,相约去两个地方,CyclicBarrier的主要作用是等待所有人都汇合了,才往下一站出发. 1 ...

  6. java 并发——线程

    一.前言 前一篇文章总结了对 java 并发中的内置锁的理解,这篇文章来说说线程 ,并发与线程总有剪不断理还乱的关系.关于 java 线程的基本概念.线程与进程的关系以及如何创建线程,想必大家都很清楚 ...

  7. 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  8. 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...

  9. Java 并发 线程的生命周期

    Java 并发 线程的生命周期 @author ixenos 线程的生命周期 线程状态: a)     New 新建 b)     Runnable 可运行 c)     Running 运行 (调用 ...

随机推荐

  1. (转)我所理解的OOP——UML六种关系

    原文地址:http://www.cnblogs.com/dolphinX/p/3296681.html 最近由于经常给公司的小伙伴儿们讲一些OOP的基本东西,每次草纸都被我弄的很尴尬,画来画去自己都乱 ...

  2. 通过定时监听input框来实现onkeyup事件-

    问题:因为zepto无法使用onkeyup 事件 解决方法:通过给input框绑定focus 事件,定时的去监听input的值得改变,在鼠标移出input后,清除定时器 <!DOCTYPE ht ...

  3. js一些算法实现

    1.约瑟夫环实现 //附有调试 function joseph(n,p){ var arr=[]; for(var i=0;i<n;i++){ arr.push(i); } debugger; ...

  4. 判断是否为闰年(bool)

    bool为布尔型,只有一个字节,取值false和true #include<iostream>using namespace std;int main(){ int year; bool ...

  5. web相关

    1. html 和 htm 的区别 如果一个网站有index.html和index.htm 默认情况下优先访问.html htm是为了兼容之前dos系统的命名规范. 2. http1.0短连接 htt ...

  6. Java中ArrayList类详解

    1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和ILis ...

  7. c# 与 winform 界面开发

    在 windows 下使用 vs2010 开发,未深入研究. c# 与 .net 开发,一堆又一堆的新名词,头晕目眩,比如 CLR / apartments / STA / MTA / COM 吐槽无 ...

  8. CSS Masking(翻译)

    原文地址:http://www.html5rocks.com/en/tutorials/masking/adobe/ 关于计算机图形,两种常见的操作是:cliping(裁剪) and  masking ...

  9. 嵌入Web资源的方法

    可以将js .图片.css等嵌入Assembly中,这样就不用将文件在aspx中写了,特别适合做自定义控件的时候将控件用到的资源打包. 将文件放到项目的合适路径,比如jpg文件所在路径的namespa ...

  10. 在阿里云服务器ubuntu14.04运行netcore

    从netcore1.0正式发布就很激动,想要赶紧学习. 最近博客园的一篇文章给了完整的指导非常感谢,但是在实际实现到发布到阿里云服务器遇到一些问题,记录下来. 首先上基础文章http://www.cn ...