当前位置: 首页 > news >正文

线程同步工具(一)控制并发访问资源

声明:本文是《 Java 7 Concurrency Cookbook 》的第三章, 作者: Javier Fernández González 译者:郑玉婷    

控制并发访问资源

这个指南,你将学习怎样使用Java语言提供的Semaphore机制。Semaphore是一个控制访问多个共享资源的计数器。

Semaphore的内容是由Edsger Dijkstra引入并在 THEOS操作系统上第一次使用。

当一个线程想要访问某个共享资源,首先,它必须获得semaphore。如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许访问共享的资源。计数器的值大于0表示,有可以自由使用的资源,所以线程可以访问并使用它们。

另一种情况,如果semaphore的计数器的值等于0,那么semaphore让线程进入休眠状态一直到计数器大于0。计数器的值等于0表示全部的共享资源都正被线程们使用,所以此线程想要访问就必须等到某个资源成为自由的。

当线程使用完共享资源时,他必须放出semaphore为了让其他线程可以访问共享资源。这个操作会增加semaphore的内部计数器的值。

在这个指南里,你将学习如何使用Semaphore类来实现一种比较特殊的semaphores种类,称为binary semaphores。这个semaphores种类保护访问共享资源的独特性,所以semaphore的内部计数器的值只能是1或者0。为了展示如何使用它,你将要实现一个PrintQueue类来让并发任务打印它们的任务。这个PrintQueue类会受到binary semaphore的保护,所以每次只能有一个线程可以打印。

准备

指南中的例子是使用Eclipse IDE 来实现的。如果你使用Eclipse 或者其他的IDE,例如NetBeans, 打开并创建一个新的java任务。

怎么做呢

按照这些步骤来实现下面的例子:


01//1.   创建一个会实现print queue的类名为 PrintQueue。
02 public class PrintQueue {
03 
04//2.   声明一个对象为Semaphore,称它为semaphore。
05 private final Semaphore semaphore;
06 
07//3.   实现类的构造函数并初始能保护print quere的访问的semaphore对象的值。
08 public PrintQueue(){
09     semaphore=new Semaphore(1);
10}
11 
12//4.   实现Implement the printJob()方法,此方法可以模拟打印文档,并接收document对象作为参数。
13 public void printJob (Object document){
14 
15//5.   在这方法内,首先,你必须调用acquire()方法获得demaphore。这个方法会抛出 InterruptedException异常,使用必须包含处理这个异常的代码。
16 try {
17     semaphore.acquire();
18 
19//6.   然后,实现能随机等待一段时间的模拟打印文档的行。
20 long duration=(long)(Math.random()*10);
21 System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().getName(),duration);
22Thread.sleep(duration);
23 
24//7.    最后,释放semaphore通过调用semaphore的relaser()方法。
25 } catch (InterruptedException e) {
26     e.printStackTrace();
27 } finally {
28     semaphore.release();
29}
30 
31//8.   创建一个名为Job的类并一定实现Runnable 接口。这个类实现把文档传送到打印机的任务。
32 public class Job implements Runnable {
33 
34//9.   声明一个对象为PrintQueue,名为printQueue。
35 private PrintQueue printQueue;
36 
37//10. 实现类的构造函数,初始化这个类里的PrintQueue对象。
38 public Job(PrintQueue printQueue){
39     this.printQueue=printQueue;
40}
41 
42//11. 实现方法run()。
43@Override
44 public void run() {
45 
46//12. 首先, 此方法写信息到操控台表明任务已经开始执行了。
47 System.out.printf("%s: Going to print a job\n",Thread. currentThread().getName());
48 
49//13. 然后,调用PrintQueue 对象的printJob()方法。
50 printQueue.printJob(new Object());
51 
52//14. 最后, 此方法写信息到操控台表明它已经结束运行了。
53 System.out.printf("%s: The document has been printed\n",Thread.currentThread().getName());
54}
55 
56//15. 实现例子的main类,创建名为 Main的类并实现main()方法。
57 public class Main {
58     public static void main (String args[]){
59 
60//16. 创建PrintQueue对象名为printQueue。
61 PrintQueue printQueue=new PrintQueue();
62 
63//17. 创建10个threads。每个线程会执行一个发送文档到print queue的Job对象。
64 Thread thread[]=new Thread[10];
65 for (int i=0; i<10; i++){
66 thread[i]=new Thread(new Job(printQueue),"Thread"+i);
67}
68 
69//18. 最后,开始这10个线程们。
70 for (int i=0; i<10; i++){
71thread[i].start();
72}

它是怎么工作的…

这个例子的关键是PrintQueue类的printJob()方法。此方法展示了3个你必须遵守的步骤当你使用semaphore来实现critical section时,并保护共享资源的访问:

1. 首先, 你要调用acquire()方法获得semaphore。
2. 然后, 对共享资源做出必要的操作。
3. 最后, 调用release()方法来释放semaphore。

另一个重点是PrintQueue类的构造方法和初始化Semaphore对象。你传递值1作为此构造方法的参数,那么你就创建了一个binary semaphore。初始值为1,就保护了访问一个共享资源,在例子中是print queue。

当你开始10个threads,当你开始10个threads时,那么第一个获得semaphore的得到critical section的访问权。剩下的线程都会被semaphore阻塞直到那个获得semaphore的线程释放它。当这情况发生,semaphore在等待的线程中选择一个并给予它访问critical section的访问权。全部的任务都会打印文档,只是一个接一个的执行。

更多…

Semaphore类有另2个版本的 acquire() 方法:

  1. acquireUninterruptibly():acquire()方法是当semaphore的内部计数器的值为0时,阻塞线程直到semaphore被释放。在阻塞期间,线程可能会被中断,然后此方法抛出InterruptedException异常。而此版本的acquire方法会忽略线程的中断而且不会抛出任何异常。
  2. tryAcquire():此方法会尝试获取semaphore。如果成功,返回true。如果不成功,返回false值,并不会被阻塞和等待semaphore的释放。接下来是你的任务用返回的值执行正确的行动。

Semaphores的公平性

fairness的内容是指全java语言的所有类中,那些可以阻塞多个线程并等待同步资源释放的类(例如,semaphore)。默认情况下是非公平模式。在这个模式中,当同步资源释放,就会从等待的线程中任意选择一个获得资源,但是这种选择没有任何标准。而公平模式可以改变这个行为并强制选择等待最久时间的线程。

随着其他类的出现,Semaphore类的构造函数容许第二个参数。这个参数必需是 Boolean 值。如果你给的是 false 值,那么创建的semaphore就会在非公平模式下运行。如果你不使用这个参数,是跟给false值一样的结果。如果你给的是true值,那么你创建的semaphore就会在公平模式下运行。

相关文章:

  • Solr schema编写指导
  • Azkaban的Web Server源码探究系列14:创建Project
  • 成都Uber优步司机奖励政策(4月11日)
  • Tomcat6.0数据源配置
  • 同步和Java内存模型(四)有序性
  • git命令的简单使用
  • lintcode:买卖股票的最佳时机 I
  • PHP处理一个5G文件,使用内存512M的,数据为整形,从大到小排序,优化排序算法...
  • c++的this指针
  • CM android的CMUpdater分析(二)
  • Saving HDU hdu
  • activiti 动态配置 activiti 监听引擎启动和初始化(高级源码篇)
  • Webview组件和HTML的介绍
  • [一句秒懂]高仿QQ底部小红点弹簧效果
  • 安全关闭多Activity的Application
  • JavaScript-如何实现克隆(clone)函数
  • 4. 路由到控制器 - Laravel从零开始教程
  • Angularjs之国际化
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  •  D - 粉碎叛乱F - 其他起义
  • Docker下部署自己的LNMP工作环境
  • Electron入门介绍
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • node和express搭建代理服务器(源码)
  • orm2 中文文档 3.1 模型属性
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • SQLServer之索引简介
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 订阅Forge Viewer所有的事件
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 前端之React实战:创建跨平台的项目架构
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 数据结构java版之冒泡排序及优化
  • 微信小程序设置上一页数据
  • 优化 Vue 项目编译文件大小
  • 源码安装memcached和php memcache扩展
  • FaaS 的简单实践
  • #QT(一种朴素的计算器实现方法)
  • #Z2294. 打印树的直径
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • *** 2003
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .net CHARTING图表控件下载地址
  • .NET Framework .NET Core与 .NET 的区别
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .net连接MySQL的方法
  • @ 代码随想录算法训练营第8周(C语言)|Day53(动态规划)
  • @JSONField或@JsonProperty注解使用
  • @Repository 注解