【java并发】线程同步工具Semaphore的使用
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的使用的更多相关文章
- Java 并发 线程同步
Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...
- 线程同步工具 Semaphore类使用案例
参考博文 : 线程同步工具(一) 线程同步工具(二)控制并发访问多个资源 并发工具类(三)控制并发线程数的Semaphore 使用Semaphore模拟互斥锁 当一个线程想要访问某个共享资源,首先,它 ...
- 线程同步工具 Semaphore类的基础使用
推荐好文: 线程同步工具(一) 线程同步工具(二)控制并发访问多个资源 并发工具类(三)控制并发线程数的Semaphore 简介 Semaphore是基于计数的信号量,可以用来控制同时访问特定资源的线 ...
- Java并发——线程同步Volatile与Synchronized详解
0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...
- Java核心知识点学习----线程同步工具类,CyclicBarrier学习
线程同步工具类,CyclicBarrier日常开发较少涉及,这里只举一个例子,以做备注.N个人一块出去玩,相约去两个地方,CyclicBarrier的主要作用是等待所有人都汇合了,才往下一站出发. 1 ...
- java 并发——线程
一.前言 前一篇文章总结了对 java 并发中的内置锁的理解,这篇文章来说说线程 ,并发与线程总有剪不断理还乱的关系.关于 java 线程的基本概念.线程与进程的关系以及如何创建线程,想必大家都很清楚 ...
- 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 秒杀多线程第八篇 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...
- Java 并发 线程的生命周期
Java 并发 线程的生命周期 @author ixenos 线程的生命周期 线程状态: a) New 新建 b) Runnable 可运行 c) Running 运行 (调用 ...
随机推荐
- Javascript,闭包
js变量的调用 一.全局变量函数调用 var n = 99; function f1() { alert(n); } f1();//99 在函数外部定义的变量,再函数中可以被调用,因为变量n,与f1在 ...
- window nodejs 版本切换 nvmw
参考文档:https://cnodejs.org/topic/5338c5db7cbade005b023c98 nvmw 下载到本地 git clone https://github.com/hako ...
- linux的终端,网络虚拟终端,伪终端(转)
转自http://www.xuebuyuan.com/877887.html 2013年09月07日 ⁄ 综合 ⁄ 共 4047字 ⁄ 字号 小 中 大 ⁄ 评论关闭 Linux上许多网络服务应用,如 ...
- 百度编辑器1.4.3 .net版在vs2008的使用方法
由于在官方下载的ueditor包是在vs2012下开发的,可以在vs2010中使用,但在vs2008中就会报错.折腾了一翻,现将解决方法分享给需要的朋友,其实就是把里面包含.net4.0的元素换成.n ...
- 学习笔记_过滤器详细(过滤器JavaWeb三大组件之一)
过滤器详细 1 过滤器的生命周期 我们已经学习过Servlet的生命周期,那么Filter的生命周期也就没有什么难度了! (l) init(FilterConfig):在服务器启动时会创建Filte ...
- C#&JQ仿网上商城商品条件筛选功能
1.后台绑定: 一种案例: 根据第一级显示第二级,并带有每个二级的“全部”功能: #region 绑定区域 #region 绑定一级区域 ) <= ? : (PageIndex - )) + , ...
- [Twisted] Test
由于Twisted程序采用事件驱动,并使用Deferred来处理事件,使用Python unittest的写测试并不容易.因此, Twisted拓展了unitest,并使用命令行工具来运行测试.这些组 ...
- JDBC实现往MySQL插入百万级数据
想往某个表中插入几百万条数据做下测试, 原先的想法,直接写个循环10W次随便插入点数据试试吧,好吧,我真的很天真.... DROP PROCEDURE IF EXISTS proc_initData; ...
- Python问题记录:如何处理中文网页中的多余空格
在制作Epub电子书的时候,因为有从网络上下载的格式比较混乱的电子书,现在打算自己用Pythonc处理一下. 1.如何删除掉网页(html)中的多余空额.尤其是包含在tag(标签:span.p)当中的 ...
- Poj 1511 Invitation Cards(spfa)
Invitation Cards Time Limit: 8000MS Memory Limit: 262144K Total Submissions: 24460 Accepted: 8091 De ...