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. 如何理解 Redux?

    作者:Wang Namelos 链接:https://www.zhihu.com/question/41312576/answer/90782136 来源:知乎 著作权归作者所有,转载请联系作者获得授 ...

  2. 20160331javaweb之JSP 标签技术

    jsp的标签技术:在jsp页面中最好不要出现java代码,这时我们可以使用标签技术将java代码替换成标签来表示 1.jsp标签:sun原生提供的标签直接在jsp页面中就可以使用 <jsp:in ...

  3. Ext.Net学习笔记06:Ext.Net DirectEvents用方补充

    在ASP.NET控件上面使用DirectEvents 我们在ASP.NET中实现无刷新的页面请求的时候,通常会用到UpdatePanel,现在Ext.Net为我们提供了另外一种渠道:通过DirectE ...

  4. JS中==和===的区别

    1.对于string,number等基础类型,==和===是有区别的 1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等 2)同类型比较,直接进 ...

  5. 抓取Bing每日图片作为网站首页背景

    把Bing搜索的背景图片设置为自己网站的背景,实现背景及资讯的每日更新 效果图如下: 理一下思路,首先我们要抓取Bing的每日图片及最新资讯,然后保存图片及信息到本地,最后显示图片及资讯到网站首页. ...

  6. OpenCV(7)-图像直方图

    直方图定义可参考这里.图像的直方图用来表示图像像素的统计信息,它统计了图像每一个通道(如果是多通道)中,每个像素的个数(比例). 计算直方图 OpenCV提供了直接计算直方图的函数 void calc ...

  7. itoa 和_itoa_s

    1> itoa, 将整数转换为字符串. char *  itoa ( int value, char * buffer, int radix ); 它包含三个参数: value, 是要转换的数字 ...

  8. Qt creator 创建鼠标右键菜单 (不新建类)

    界面 步骤 打开你的界面文件并选中你要添加右键的控件,选择“CustomContextMenu” 右键选择“转到槽...” -> customContextMenuRequested 插入下面代 ...

  9. ECMA中关于if与else的关系的一句英文,感觉比较经典

    Each else for which the choice of assocated if is ambiguous shall be associated with the nearest pos ...

  10. varchar(n),nvarchar(n) 长度、性能、及所占空间分析 nvarchar(64) nvarchar(128) nvarchar(256)(转)

    varchar(n),nvarchar(n) 中的n怎么解释: nvarchar(n)最多能存n个字符,不区分中英文. varchar(n)最多能存n个字节,一个中文是两个字节. 所占空间: nvar ...