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. SQL语句修改表

    -- 更改字段类型 默认值 alter table 表名 alter column 字段名 类型 ALTER TABLE 表名 add DEFAULT ('修改后的默认值') for 字段名 WITH ...

  2. Fetch的使用

    import React,{ Component } from 'react'; import { AppRegistry, ListView, Image, Text, StyleSheet, Vi ...

  3. Unity3D 之3D游戏角色控制器运动

    3D运动,绑定了人形控制器后的一个简单的运动方法. using UnityEngine; using System.Collections; public class PlayerMove : Mon ...

  4. JSON 与List转换类封装

    json与list转换小结: import java.util.ArrayList; import java.util.List; import com.google.gson.Gson; impor ...

  5. 用友U8按BOM计算销售订单物料需求SQL代码 第一稿

    drop table #tmp1999 drop table #tmp2999 drop table #tmp3999 drop table #tmp4999 drop table #tmp5999 ...

  6. ASP.NET问题处理---“数据请求超时错误“”

    数据请求超时,一般有2中解决方式: 1.页面AJAX处理数据时延长时间: 2.后台数据库连接取数据时延长时间. 由于我的后台数据库连接取数据为循环读取数据,所以不存在超时问题,这里具体说说如何修改AJ ...

  7. 免费vpn

    http://www.freevpnmac.com/macvpn/ 下边有个滑动解锁,拖动鼠标至右边解锁,点击Get vpn info,得到vpn地址ip和用户密码. 我用安卓手机测试了下完全可以,但 ...

  8. java中collection、map、set、list简介 (转)

    Collection接口  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元 ...

  9. ES6的编码风格

    编程风格 [转自http://es6.ruanyifeng.com/#docs/style] 块级作用域 字符串 解构赋值 对象 数组 函数 Map结构 Class 模块 ESLint的使用 本章探讨 ...

  10. 【原创】Linux下获取命令的帮助与常用命令

    Linux中的shell命令一般是执行步骤:用户在终端输入命令回车,系统内核会在当前用户的环境变量PATH中去读取环境变量的值 变量的值就是命令的路径,命令路径不只一个,于是系统会从这些路径中从左至右 ...