Java并发编程实战————Semaphore信号量的使用浅析

引言

本篇博客讲解《Java并发编程实战》中的同步工具类:信号量 的使用和理解。

从概念、含义入手,突出重点,配以代码实例及讲解,并以生活中的案例做类比加强记忆。

什么是信号量

Java中的同步工具类信号量即计数信号量(Counting Semaphore),是用来控制访问某个特定资源的操作数量,或同时执行某个指定操作的数量。可以简单理解为信号量用来限制对某个资源的某种操作的数量。

一般用于实现某种资源池,或对容器施加边界。

 信号量管理着一组有限个数的虚拟许可(permit),而许可的数量就是限制特定操作数量的关键。

信号量的使用

前面已经说过,信号量一般用于实现某种资源池或对容器施加边界,这都是一个对特定操作的限制用途。那么想象一下,如何限制操作的数量,达到为一个再普通不过的容器施加边界的效果呢?答案是给容器的某种操作(可以是添加或删除元素,应该广义的理解“某种操作”这个关键字眼)增加一道执行许可,只有在获得许可的情况下才可以执行这个操作:

上图左边是普通的对容器的操作,右边是有了信号量的对容器的操作。可以看出,在增加了中间的信号量之后,对容器的操作将会受限。

Semaphore

了解了信号量的大概含义,那么进一步深入到Java类库的层面,JDK为开发者提供了java.util.concurrent包下的Semaphore类,它的含义就是上面所述的信号量,管理着一组permit。

以“为容器施加边界”这一信号量用途为例。首先我们要明确一点,使用信号量的方式来实现施加边界的方式,其针对的是操作而不是容器的容量!再一次重申,是限制了操作,而不是容器的容量!

强调限制操作,是为了要明白一点:使用信号量来施加边界,必然会对这个容器的某些操作进一步封装。比如添加方法,就会在调用add之前先行调用Semaphore对象的acquire()方法,在与这个操作相反的操作中去release()。并且,acquire()方法是阻塞式的,这就代表没有闲置许可的时候,操作将会阻塞直到有许可被释放。

下面代码用信号量来对HashSet这个最普通的容器来施加一个添加限制,进一步封装,使其成为一个有界的阻塞式的容器

public class BoundedHashSet<T> {private final Set<T> set;private final Semaphore sem;public BoundedHashSet(int bound) {this.set = Collections.synchronizedSet(new HashSet<>());this.sem = new Semaphore(bound);}public boolean add(T o) throws InterruptedException {sem.acquire();boolean wasAdded = false;try {wasAdded = set.add(o);return wasAdded;} finally {if (!wasAdded)sem.release();}}public boolean remove(Object o) {boolean wasRemoved = set.remove(o);if (wasRemoved)sem.release();return wasRemoved;}/** 只是为了方便打印的 */public void print() {System.out.print(Thread.currentThread().getName() + " : ");this.set.forEach(o -> System.out.print(o + " "));}/** 用于测试的主方法 */public static void main(String[] args) {BoundedHashSet<String> names = new BoundedHashSet<>(5);new Thread(() -> {for (int i = 0; i < 100; i++) {try {names.add("name" + i);names.print();System.out.println();TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}}}, "TH-ADD").start();new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--------执行清理,删除name" + i);names.remove("name" + i);try {TimeUnit.SECONDS.sleep(10);} catch (Exception e) {e.printStackTrace();}}},"TH-REMOVE").start();}
}

执行结果如下:

我们用一个线程为这个“有界”容器每隔1秒钟添加一个元素,然后另一个线程每隔10秒钟移除一个元素。且初始化了这个容器的信号量为5,那么当容器中添加元素的数量达到5之后,5个许可全部被占用,添加操作将进入阻塞状态,直到remove的时候释放一个许可,才可以继续添加元素。从上述结果可以看出两点:

1、拥有5个许可的信号量成功的限制了容器的元素个数(即为容器施加了一个边界);

2、添加的操作在没有获得许可的情况下将进入阻塞状态,在执行的过程中也恰恰印证了这一点:当remove执行并release()之后,添加操作会立刻执行。

生活中的类比

其实这个类比博主认为,从严谨的角度来讲,并不是完全符合信号量的概念,但是我们可以类比的同时找出不同点,不仅有效的通过生活案例理解了信号量,还对与之不同的地方增加了深刻的印象,所以还是决定拿出来供大家参考。

上过学的同学可能都知道,学校有奖学金制度。虽然我没怎么得过奖学金,但是大概的逻辑还是比较好理解。

学校的奖学金制度是怎样的呢?

学校每年都会给全校的学生指定数量的全额奖学金名额,比如全额奖学金5名。那么如果想获得全额奖学金,就必须先获得名额才行。

从这个简单的逻辑我们可以找出关键的与信号量中的概念相匹配的内容:

奖学金 = 特定资源

获得(奖学金) = 指定操作(如remove操作)

名额 = 一组定额许可的信号量

名额已满,来年再报 = 操作阻塞,等待释放许可

 有了上面的等式,信号量的神秘面纱就算彻底被我们揭开了,原来它就是一个管理一组定额许可的通行证,要想执行操作,那就必须先得到许可,否则就阻塞。

总结

信号量的概念:限制操作数量。

一个类:Semaphore ,两个方法:acquire()、release()。

用途:对容器施加边界,对容器的操作的再封装。

另外,奖学金和信号量之间的类比并不完全匹配,不过这种程度的类比已经相当清晰,至于哪些信息有所差异,留给各位看官自己去挖掘。如果有什么新的发现,真诚希望在博客下方留言。

愿所有热爱编程的开发者共同进步!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Effective Java(一)———— 代替构造器和Setter的构建器模式

引言 Java语言中的一部经典著作《Effective Java》&#xff0c;里面涵盖了78条我们应该熟练的Java编程技巧。 本篇博客是该书学习的系列笔记第一篇。本系列博客不会与书中的78条建议完全匹配。只是以一种读者的身份来记录和总结从书中得到的好的编程建议&#xff0c;博客中会…

LeetCode算法入门- Palindrome Number-day2

LeetCode算法入门- Palindrome Number-day2 Palindrome Number Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward. Example 1: Input: 121 Output: true Example 2: Input: -121 Output: false Ex…

JavaCard概述

什么是JavaCard JavaCard&#xff0c;即Java智能卡。以智能卡硬件系统为基础&#xff0c;通过软件的方式构造一个支持Java程序下载、安装、运行的软/硬件系统。由于引入了虚拟机技术&#xff0c;JavaCard具备硬件无关性&#xff0c;即智能卡应用程序开发与智能卡硬件系统相分离…

LeetCode算法入门- Add Two Numbers-day3

LeetCode算法入门- Add Two Numbers-day3 Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return …

Java并发编程实战————并发技巧小结

可变状态是至关重要的。所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少&#xff0c;就越容易确保线程安全性。尽量将域声明为final类型&#xff0c;除非需要它们是可变的。不可变对象一定是线程安全的。不可变对象能极大地降低并发编程的复杂性。它们更为简…

Java核心篇之多线程---day1

Java面试之多线程—day1 一. 线程中sleep方法与wait方法有什么区别&#xff1f; 对于 sleep()方法&#xff0c;我们首先要知道该方法是属于 Thread 类中的。而 wait()方法&#xff0c;则是属于Object 类中的。 在调用 sleep()方法的过程中&#xff0c; 线程不会释放对象锁。而…

LeetCode算法入门- Longest Substring Without Repeating Characters-day4

LeetCode算法入门- Longest Substring Without Repeating Characters-day4 Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. Example 1: Input: “abcabcbb” Output: 3 Explana…

Java 多线程 —— 常用并发容器

引言 本博客基于常用的并发容器&#xff0c;简单概括其基本特性和简单使用&#xff0c;并不涉及较深层次的原理分析和全面的场景用法。 适合对不了解并发容器的同学&#xff0c;工作中遇到类似的场景&#xff0c;能够对文中提到的并发容器留有简单印象就好。 一、Concurrent…

LeetCode算法入门- Longest Palindromic Substring-day5

LeetCode算法入门- Longest Palindromic Substring-day5 Longest Palindromic Substring Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Example 1: Input: “babad” Output: “bab” Note: “a…

Tomcat运行三种模式:http-bio|http-nio|http-apr介绍

转自《tomcat运行三种模式:http-bio|http-nio|http-apr介绍》 Tomcat是一个小型的轻量级应用服务器&#xff0c;也是JavaEE开发人员最常用的服务器之一。不过&#xff0c;许多开发人员不知道的是&#xff0c;Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式&#…

LeetCode算法入门- Reverse Integer-day6

LeetCode算法入门- Reverse Integer-day6 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 Example 2: Input: -123 Output: -321 Example 3: Input: 120 Output: 21 class Solution {public int reverse(int x) {long…

Java工具方法——属性拷贝方法:BeanUtils.copyProperties(Object, Object)

介绍 org.springframework.beans.BeanUtils.copyProperties(Object, Object)是spring 框架的对象工具类&#xff1a;BeanUtils下的一个拷贝对象属性的方法。 官方注释 把给定的源对象属性值拷贝到目标对象中。 注意&#xff1a;源对象类与目标对象类不一定非要完全匹配&…

LeetCode算法入门- String to Integer (atoi)-day7

LeetCode算法入门- String to Integer (atoi)-day7 String to Integer (atoi): Implement atoi which converts a string to an integer. The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. The…

LeetCode算法入门- Roman to Integer Integer to Roman -day8

LeetCode算法入门- Roman to Integer -day8 Roman to Integer&#xff1a; 题目描述&#xff1a; Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M. Symbol    Value I        1 V        5 X        10 L…

Git初学札记(九)————EGit检出远程分支

引言 现在有这样一个使用场景&#xff1a;团队中的其他开发者提交了一个新的特性分支&#xff08;如feature_1&#xff09;&#xff0c;要求我们一同开发&#xff0c;并将自己修改的代码也全部提交到这个分支上去。那么如何将这个分支检出&#xff0c;并将本地检出的分支与这个…

LeetCode算法入门- 3Sum -day9

LeetCode算法入门- 3Sum -day9 题目描述&#xff1a; Given an array nums of n integers, are there elements a, b, c in nums such that a b c 0? Find all unique triplets in the array which gives the sum of zero. Note: The solution set must not contain dup…

XML学习(一)————XML简介

引言 作为数据传输界鼎鼎大名的扛把子&#xff0c;XML被应用于各个方面&#xff0c;但随着弱结构化标记语言如JSON、YAML等的出现&#xff0c;人们慢慢的脱离了XML的统治&#xff0c;但在互联网早期的发展当中XML是不可或缺的一部分&#xff0c;比如各种微信开发中的数据传输&…

Java核心篇之Java锁--day2

Java核心篇之Java锁–day2 乐观锁&#xff1a;乐观锁是一种乐观思想&#xff0c;即认为读多写少&#xff0c;每次去取数据的时候都认为其他人不会修改&#xff0c;所以不会上锁&#xff1b;但是在更新的时候会判断一下在此期间别人有没有去修改它&#xff0c;如果有人修改的话…

XML学习(二)————属性还是标签?

引言 xml中并没有规则要求我们什么时候使用属性&#xff0c;什么时候使用标签。 属性和标签都可以存储数据&#xff0c;但是在XML的使用中&#xff0c;我们需要探讨一下对属性和标签的选择问题。 约定规则 XML 应该避免使用属性来存储数据&#xff0c;这与HTML的推荐规则不…

LeetCode算法入门- 3Sum Closest -day10

LeetCode算法入门- 3Sum Closest -day10 Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one …