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

简介:

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

控制并发访问多个资源

在并发访问资源的控制中,你学习了信号量(semaphores)的基本知识。

在上个指南,你实现了使用binary semaphores的例子。那种semaphores是用来保护访问一个共享资源的,或者说一个代码片段每次只能被一个线程执行。但是semaphores也可以用来保护多个资源的副本,也就是说当你有一个代码片段每次可以被多个线程执行。

在这个指南中,你将学习怎样使用semaphore来保护多个资源副本。你将实现的例子会有一个print queue但可以在3个不同的打印机上打印文件。

准备

指南中的例子是使用 Eclipse IDE 来实现的。如果你使用Eclipse 或者其他的IDE,例如NetBeans, 打开并创建一个新的java任务。实现在控制并发访问资源里描述的例子。

怎么做呢

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

01 //1.  如我们之前提到的,你将实现semaphores来修改print queue例子。打开PrintQueue类并声明一个boolean array名为 freePrinters。这个array储存空闲的等待打印任务的和正在打印文档的printers。
02 private boolean freePrinters[];
03  
04 //2.   接着,声明一个名为lockPrinters的Lock对象。将要使用这个对象来保护freePrinters array的访问。
05 private Lock lockPrinters;
06  
07 //3.   修改类的构造函数并初始化新声明的对象们。freePrinters array 有3个元素,全部初始为真值。semaphore用3作为它的初始值。
08 public PrintQueue(){
09  
10 semaphore=new Semaphore(3);
11 freePrinters=new boolean[3];
12  
13 for (int i=0; i<3; i++){
14     freePrinters[i]=true;
15 }
16 lockPrinters=new ReentrantLock();
17 }
18  
19 //4.   修改printJob()方法。它接收一个称为document的对象最为唯一参数。
20 public void printJob (Object document){
21  
22 //5.   首先,调用acquire()方法获得semaphore的访问。由于此方法会抛出 InterruptedException异常,所以必须加入处理它的代码。
23 try {
24     semaphore.acquire();
25  
26 //6.   接着使用私有方法 getPrinter()来获得被安排打印任务的打印机的号码。
27 int assignedPrinter=getPrinter();
28  
29 //7.    然后, 随机等待一段时间来实现模拟打印文档的行。
30 long duration=(long)(Math.random()*10);
31 System.out.printf("%s: PrintQueue: Printing a Job in Printer%d during %d seconds\n",Thread.currentThread().getName(), assignedPrinter,duration);
32 TimeUnit.SECONDS.sleep(duration);
33  
34 //8.   最后,调用release() 方法来解放semaphore并标记打印机为空闲,通过在对应的freePrinters array引索内分配真值。
35 freePrinters[assignedPrinter]=true;
36 catch (InterruptedException e) {
37     e.printStackTrace();
38 finally {
39     semaphore.release();
40 }
41  
42 //9.  实现 getPrinter() 方法。它是一个私有方法,返回一个int值,并不接收任何参数。
43 private int getPrinter() {
44  
45 //10. 首先,声明一个int变量来保存printer的引索值。
46 int ret=-1;
47  
48 //11. 然后, 获得lockPrinters对象 object的访问。
49 try {
50 lockPrinters.lock();
51  
52 //12. 然后,在freePrinters array内找到第一个真值并在一个变量中保存这个引索值。修改值为false,因为等会这个打印机就会被使用。
53 for (int i=0; i<freePrinters.length; i++) {
54 if (freePrinters[i]){
55     ret=i;
56     freePrinters[i]=false;
57     break;
58 }
59 }
60  
61 //13. 最后,解放lockPrinters对象并返回引索对象为真值。
62 catch (Exception e) {
63     e.printStackTrace();
64 finally {
65     lockPrinters.unlock();
66 }
67 return ret;
68  
69 //14. Job 和 Core 类不做任何改变。

它是怎么工作的…

在例子中的PrintQueue类的关键是:Semaphore对象创建的构造方法是使用3作为参数的。这个例子中,前3个调用acquire() 方法的线程会获得临界区的访问权,其余的都会被阻塞 。当一个线程结束临界区的访问并解放semaphore时,另外的线程才可能获得访问权。

在这个临界区,线程获得被分配打印的打印机的引索值。例子的这部分让例子更真实,而且它没有使用任何与semaphores相关的代码。以下的裁图展示了这个例子的执行输出:

每个文档都被安排到第一个空闲的打印机打印。

更多…

The acquire(), acquireUninterruptibly(), tryAcquire(),和release()方法有一个外加的包含一个int参数的版本。这个参数表示 线程想要获取或者释放semaphore的许可数。也可以这样说,这个线程想要删除或者添加到semaphore的内部计数器的单位数量。在这个例子中acquire(), acquireUninterruptibly(), 和tryAcquire() 方法, 如果计数器的值小于许可值,那么线程就会被阻塞直到计数器到达或者大于许可值。

参见

第三章,线程同步应用:并发地访问资源的控制
第八章,同步应用的测试:修改Lock接口
第二章,基本线程同步:修改lock的公平性

文章转自 并发编程网-ifeve.com

目录
相关文章
|
6天前
|
存储 缓存 安全
JUC并发—11.线程池源码分析
本文主要介绍了线程池的优势和JUC提供的线程池、ThreadPoolExecutor和Excutors创建的线程池、如何设计一个线程池、ThreadPoolExecutor线程池的执行流程、ThreadPoolExecutor的源码分析、如何合理设置线程池参数 + 定制线程池。
JUC并发—11.线程池源码分析
|
4月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
6月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
104 6
|
6月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
148 8
|
6月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
3月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
76 17
|
3月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
74 26
|
5月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
405 2
|
6月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
6月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####