【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 运行 (调用 ...
随机推荐
- 关于利用动态代理手写数据库连接池的异常 java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection
代码如下: final Connection conn=pool.remove(0); //利用动态代理改造close方法 Connection proxy= (Connection) Proxy.n ...
- Candence下对“跨页连接器(off-page connector)”进行批量重命名的方法
parts.ports.alias等等均可以在“属性编辑器(Property Editor)”中进行查看编辑,并通过复制到Excel等表格软件来进行批量修改.之后再粘贴回去的方法进行批量编辑.但是“跨 ...
- 怎样区分JQuery对象和Dom对象 常用的写法
第一步,http://www.k99k.com/jQuery_getting_started.html 第二步,新手先仔细得全部看一遍jQuery的选择器,很重要!!! (http://shawphy ...
- Python GUI with Tkinter (from youtube) 在youtube上能找到很多编程视频...
Python GUI with Tkinter - 1 - Introduction以上链接是一个python tkinter视频系列的第一讲的链接.虽然英语不好,但是,程序还是看得懂的(照着做就可以 ...
- php 加载函数 __autoload(), spl_autoload_register()
来自:http://www.cnblogs.com/myluke/archive/2011/06/25/2090119.html spl_autoload_register (PHP 5 >= ...
- 浏览器中输入URL到返回页面的全过程
第一步,解析域名,找到主机IP (1)浏览器会缓存DNS一段时间,一般2-30分钟不等.如果有缓存,直接返回IP,否则下一步. (2)缓存中无法找到IP,浏览器会进行一个系统调用,查询hosts文件. ...
- python之全栈开发——————IO模型
一:在讲IO模型之前我们首先来讲一下事件驱动模型,属于一种编程的范式,那么我们以前就是传统式编程,来看看有什么区别吧(此处为借鉴别人的) 传统的编程是如下线性模式的: 开始--->代码块A--- ...
- C#中的反射 Assembly.Load() Assembly.LoadFrom()
一些关于C#反射的知识,估计也就最多达到使用API的程度,至于要深入了解,以现在的水平估计很难做到,所以下面此篇文章,以作为一个阶段的总结. 对于反射的总结,我想从以下几个方面展开,首先是反射程序集, ...
- DataGridView出现大红叉--在使用多线程访问数据源时
datagridview 的数据源操作在一个方面里面处理 不要多个地方处理 并且处理的时候要加锁 红叉 应该是多线程操作出现的. try catch 只是起到 捕获异常的功能,但是一旦出现了这种错误 ...
- CUDA获取显卡数据
一个简单的获取Nvidia显卡信息的程序 #include<iostream> int main() { cudaDeviceProp prop; int count; cudaGetDe ...