Java Concurrency - Semaphore 信号量
Semaphore 是一个控制访问多个共享资源的计数器。
当一个线程想要访问某个共享资源,首先,它必须获得 semaphore。如果 semaphore 的内部计数器的值大于 0,那么 semaphore 减少计数器的值并允许访问共享的资源。计数器的值大于 0 表示,有可以自由使用的资源,所以线程可以访问并使用它们。另一种情况,如果 semaphore 的计数器的值等于 0,那么 semaphore 让线程进入休眠状态一直到计数器大于 0。计数器的值等于 0 表示全部的共享资源都正被线程们使用,所以此线程想要访问就必须等到某个资源成为自由的。当线程使用完共享资源时,它必须释放 semaphore 以便其他线程可以访问共享资源。这个操作会增加 semaphore 的内部计数器的值。
二进制信号量
二进制信号量(binary semaphores)是一种比较特殊的信号量,这种信号量只保护访问唯一的共享资源,它的内部计数器值只能是 1 或者 0。
假设有若干个线程排队执行打印任务,打印机在同一时间只能执行单个任务,打印过程中其他线程只能挂起等待。
public class Printer {
private final Semaphore semaphore;
public Printer() {
semaphore = new Semaphore(1);
}
public void print(Object doc) {
try {
semaphore.acquire();
Long duration = (long) (Math.random() * 10);
System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(), duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
public class PrintJob implements Runnable {
private Printer printer;
public PrintJob(Printer printer) {
this.printer = printer;
}
@Override
public void run() {
System.out.printf("%s: Going to print a job\n", Thread.currentThread().getName());
printer.print(new Object());
System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
}
}
public class Main {
public static void main (String args[]){
Printer printer = new Printer();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++){
threads[i] = new Thread(new PrintJob(printer), "Thread" + i);
threads[i].start();
}
}
}
运行结果:
Thread1: Going to print a job
Thread6: Going to print a job
Thread9: Going to print a job
Thread7: Going to print a job
Thread8: Going to print a job
Thread4: Going to print a job
Thread5: Going to print a job
Thread3: Going to print a job
Thread0: Going to print a job
Thread2: Going to print a job
Thread1: PrintQueue: Printing a Job during 7 seconds
Thread1: The document has been printed
Thread6: PrintQueue: Printing a Job during 5 seconds
Thread6: The document has been printed
Thread9: PrintQueue: Printing a Job during 4 seconds
Thread9: The document has been printed
Thread7: PrintQueue: Printing a Job during 8 seconds
Thread7: The document has been printed
Thread8: PrintQueue: Printing a Job during 6 seconds
Thread8: The document has been printed
Thread4: PrintQueue: Printing a Job during 2 seconds
Thread4: The document has been printed
Thread5: PrintQueue: Printing a Job during 0 seconds
Thread5: The document has been printed
Thread3: PrintQueue: Printing a Job during 7 seconds
Thread3: The document has been printed
Thread0: PrintQueue: Printing a Job during 0 seconds
Thread0: The document has been printed
Thread2: PrintQueue: Printing a Job during 5 seconds
Thread2: The document has been printed
这个例子的关键是 Printer 类的 print() 方法。此方法展示了当你使用 semaphore 来实现临界区以保护共享资源时必须遵守的三个步骤:
- 首先,调用 acquire() 方法获得信号量;
- 然后,操作共享资源;
- 最后,调用 release() 方法释放信号量。
另一个重点是 Printer 类的构造方法和初始化 Semaphore 对象。将信号量初始化为 1,就是创建了一个二进制信号量。二进制信号量只保护一个共享资源,这个例子中就是 printer。当开始 10 个线程时,第一个获得 semaphore 的线程就获得临界区的访问权,其他线程都会被 semaphore 阻塞,直到获得 semaphore 的线程释放它。当 semaphore 被释放时,它会在等待的线程中选择其中一个并赋予其临界区的访问权。所有的打印任务都会被执行,只是它们需要排队,一个接一个地执行。
控制并发访问多个共享资源
在上个例子,使用二进制信号量来保护一个共享资源。但是,信号量也可以用来保护多个共享资源。下面的类使用信号量控制对内容池的访问:
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
公平模式
与 ReentrantLock 一样,Semaphore 也提供一个接收 fair 参数的构造方法。当此参数置为 true 时,Semaphore 保证等待时间最久的线程优先获得信号量。
Semaphore 的方法
Semaphore 类有另外 2 个版本的 acquire() 方法:
- acquireUninterruptibly():acquire()方法是当 semaphore 的内部计数器的值为 0 时,阻塞线程直到 semaphore 被释放。在阻塞期间,线程可能会被中断,然后此方法抛出 InterruptedException 异常。而此版本的 acquire 方法会忽略线程的中断而且不会抛出任何异常。
- tryAcquire():此方法会尝试获取 semaphore。如果成功,返回true。如果不成功,返回 false 值,并不会被阻塞和等待 semaphore 的释放。
acquire、acquireUninterruptibly、tryAcquire 和 release 方法有一个外加的包含一个 int 参数的版本。这个参数表示线程想要获取或者释放 semaphore 的许可数。
Java Concurrency - Semaphore 信号量的更多相关文章
- java多线程-Semaphore信号量使用
介绍 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确.合理的使用公共资源. 概念 Semaphore分为单值和多值两种,前者 ...
- Java中Semaphore(信号量)的使用
Semaphore的作用: 在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了 ...
- java多线程----Semaphore信号量
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util ...
- Java中Semaphore(信号量) 数据库连接池
计数信号量用来控制同时访问某个特定资源的操作数或同时执行某个指定操作的数量 A counting semaphore.Conceptually, a semaphore maintains a set ...
- java中的信号量Semaphore
Semaphore(信号量)充当了操作系统概念下的“信号量”.它提供了“临界区中可用资源信号量”的相同功能.以一个停车场运作为例.为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的.这时如果 ...
- java笔记--对信号量Semaphore的理解与运用
java Semaphore 信号量的使用: 在java中,提供了信号量Semaphore的支持. Semaphore类是一个计数信号量,必须由获取它的线程释放, 通常用于限制可以访问某些资源(物理或 ...
- Java并发编程笔记之Semaphore信号量源码分析
JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...
- java并发之(4):Semaphore信号量、CounDownLatch计数锁存器和CyclicBarrier循环栅栏
简介 java.util.concurrent包是Java 5的一个重大改进,java.util.concurrent包提供了多种线程间同步和通信的机制,比如Executors, Queues, Ti ...
- java架构之路(多线程)JUC并发编程之Semaphore信号量、CountDownLatch、CyclicBarrier栅栏、Executors线程池
上期回顾: 上次博客我们主要说了我们juc并发包下面的ReetrantLock的一些简单使用和底层的原理,是如何实现公平锁.非公平锁的.内部的双向链表到底是什么意思,prev和next到底是什么,为什 ...
随机推荐
- opencv 矩阵的相似性对比 (图片之间比较)
测试图片: code: #include <opencv\cv.h> #include <opencv\highgui.h> #include <opencv\c ...
- UVa 10817 Headmaster's Headache (状压DP+记忆化搜索)
题意:一共有s(s ≤ 8)门课程,有m个在职教师,n个求职教师.每个教师有各自的工资要求,还有他能教授的课程,可以是一门或者多门. 要求在职教师不能辞退,问如何录用应聘者,才能使得每门课只少有两个老 ...
- C++中标准容器Vector,元素操作.insert()小结
insert() 函数有以下三种用法: iterator insert( iterator loc, const TYPE &val ); //在指定位置loc前插入值为val的元素,返回指 ...
- jsp验证码点击刷新
<img src="<%=basePath%>manage/code" alt="验证码" height="20" ali ...
- MyBatis之三:多表联合查询
在这篇文章里面主要讲解如何在mybatis里面使用一对一.一对多.多表联合查询(类似视图)操作的例子. 注:阅读本文前请先大概看一下之前两篇文章. 一.表结构 班级表class,学生表student, ...
- 手机NFC通信的安全车钥匙
SmartKeys for Cyber-Cars:Secure Smartphone-based NFC-enabled Car Immobicizer 手机NFC通信的安全车钥匙 1概述 如今,智能 ...
- [Jest] Track project code coverage with Jest
Jest comes pre-packaged with the ability to track code coverage for the modules you're testing, but ...
- SSH框架之Struts(2)——Struts的执行流程之配置文件
上篇我们大致了解了一下採用了Struts框架的web页面运行流程. 接下来的几篇我们通过Struts的源代码来学习一下Struts的内部原理. 当server启动的时候.server会依据配置文件初始 ...
- Particle designer 粒子工具中属性对应功能的简单介绍
粒子配置 Max Particles 粒子的数量 一般而言,我们的目标是用最少的粒子创造出所需的效果.单个粒子的大小对游戏运行效率也有很大的影响——单个粒子越小,性能越高. Lifespan 生命周 ...
- javascript面向对象学习笔记——创建对象(转)
工厂模式 该模值抽象了创建具体对象de过程.用函数来封装噫特定接口创建对象的细节. function createPerson(name,age,job){ var o=new Object(); o ...