Java 9 并发编程实战(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-24 15:24:07

点击下载

作者:(西班牙)哈维尔·费尔南德兹·冈萨雷斯(Javier Fernández González )

出版社:人民邮电出版社有限公司

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Java 9 并发编程实战

Java 9 并发编程实战试读:

前言

用户在使用计算机时,可以一次处理多件事情,例如,在文字处理器中编辑文字和阅读邮件的同时,还可以听音乐。之所以可以这样,是因为操作系统支持多任务的并发执行。并发编程是指使用平台提供的一些元素和机制,使多个任务可以同时进行,并且相互通信实现数据交换和同步。Java是一个并发平台,它提供很多在Java程序中执行并发任务的类。在每个版本中,Java都会为开发者增加一些功能,便于并发程序的开发。本书涵盖了Java 9并发API中最重要和实用的机制,以便于读者在程序中直接应用。这些机制包括:● 线程基本管理;● 线程同步机制;● 委派执行器进行线程创建和管理;● 使用fork/join 框架提升应用性能;● 使用并行流并行处理大数据集合,包含Java 9中新的响应流;● 并发应用的数据结构;● 调整并发类的一些默认行为,适应并发应用的测试需求;● 测试Java并发应用程序。本书内容

第1章介绍线程的基本操作。通过基本案例介绍线程的创建、执行、状态及管理。

第2章涵盖Java同步代码的基本机制。这一章会详细介绍Lock和synchronized关键字。

第3章介绍在Java中线程间同步的高级工具,主要详解如何使用Phaser类同步多阶段任务。

第4章阐述如何将线程管理委派给执行器,包括线程运行、管理、获取并发任务执行结果。

第5章阐述fork/join框架的使用。该框架是由执行器提供的一种特殊的框架,旨在使用分而治之技术将任务分割为更小的子任务。

第6章阐述如何创建流并使用中间和终端操作来并行且高效地处理一个大数据集合。Java 8引入了流这一工具,Java 9则添加了部分新接口来实现反应式流。

第7章阐述如何使用Java提供的部分并发数据结构。这些数据结构可以用在并发程序中来规避同步代码块的使用。

第8章阐述如何根据用户的需要扩展Java并发API中最常用的部分类。

第9章阐述如何获取Java 7并发API中最常用的数据结构的一些状态信息。读者可以了解到如何使用一些免费工具(如Eclipse、NetBeans IDE或是FindBugs)来调试并发程序和找出程序中可能的bug。

第10章阐述各章中对于同步、Executor、fork/join框架、并发数据结构和监控并发对象等未包含的一些概念。

第11章阐述程序员在开发并发程序时的一些注意事项。阅读本书前的准备工作

为了能更好地学习本书,你需要了解与Java编程语言相关的基本知识,此外还需要了解如何至少会使用一种IDE,如Eclipse或是NetBeans,但这并不是一个必要条件。致读者

如果你是一位有兴趣提高并发编程和多线程知识水平并乐于发现Java 8和Java 9并发新特性的Java开发者,那么这本书就是为你准备的。如果你已经熟悉了一般的Java开发实践,那么掌握线程的基本知识将是一个优势。本书结构

在本书中,你会发现很多频繁出现的标题(项目准备、案例实现、结果分析、其他说明、参考阅读)。

为了清楚说明如何完成一个案例,我们将采用以下形式。项目准备

本部分介绍案例中可预见的内容,并介绍如何设置所需的任何软件或初步设置。案例实现

本部分包含完成一个案例所需的步骤。结果分析

本部分通常会对“案例实现”中所有发生的事情进行详细解释。其他说明

本部分包含有关案例的其他信息,以便让读者更熟悉实战。参考阅读

本部分给出了有助于了解案例的其他信息的有关章节。体例

在本书中,会出现许多文本样式以区分不同类型的信息。下面是这些样式的一些示例及其含义。

代码单词的文本、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL、用户输入内容和编程短文将按如下形式进行显示,如:“执行main()方法的代码。”

代码以如下样式展示:Thread task=new PrimeGenerator();task.start();

新名词和重要的词以黑体显示。例如,在菜单或对话框中,你在计算机屏幕上看到的单词会显示在文本中,如“通过单击〖〓strong〓〗File〖〓/strong〓〗菜单下的〖〓strong〓〗New Project〖〓/strong〓〗选项创建新项目”。  警告或重要提示将在此文本框中展示。

   提示和技巧将在此文本框中展示。译者序

随着移动互联网用户量的快速增长以及大数据、人工智能的兴起,开发者对程序的并发要求不断提高,并发研究与应用也成了行业内的热点与难点。因此,第一次接触本书,我们便产生了浓厚的兴趣,大家一拍即合,于是便趁热打铁地着手翻译工作。

本书的翻译工作耗时数月,为了能给大家展现最新、最完善的内容,保证翻译质量,我们分工协作,采用软件工程里的项目管理方式,分工翻译、交叉校对、提问题,不断推进翻译的进展。本书翻译工作的顺利完成离不开每位团队成员的辛勤付出。翻译期间,浮白(王利东)经历了紧张的项目迭代工作,为保证翻译的质量,他利用春节假期修改书稿;小护士(李梓峰)广博的见识让人赞叹,为翻译工作提供了扎实的依据;千山(徐江溢)和漫游鹰(夏钰辉)对内容的深刻理解和对翻译工作的认真态度让人敬佩。在翻译过程中,我们忠于原著,力求做到译文与原著相贴合,力求展现作者对Java并发的理解。我们对本书每个案例进行了真实测试,对于原著中存在的问题进行了小心求证,并就书中存在的相关问题与作者进行了积极沟通。但限于翻译水平有限,书中难免会有不足之处,敬请广大读者指正。

我热爱技术,在参加工作之初,我就对自己说,要在技术的道路上一直走下去。但理想与现实往往是背离的,生活、工作和自我,都像失控的火车,带我去到未知的地方。

直到半年前,跟几位仁兄一起翻译本书的工作,让我重新唤起内心对技术的渴望。这段时间,我经历了一直工作到凌晨的日子。2018年的春节,我推掉了所有应酬,专注于翻译,终于完成翻译工作。

感谢几位小伙伴,你们在我严重延期时默默地支持我,这对我很重要。感谢我的家人,你们的付出让我能安心地工作。——王利东 花名:浮白

从业近5年,这是我首次参与技术书的翻译工作,新奇的同时也会心有忐忑。翻译期间,因为自身水平和其他事情曾一度导致进度滞后,所幸不辱使命,我还是在最后期限前顺利完成了翻译工作。

我在西小口开始本书的翻译,在后厂村完成本书的交付。我曾在夜深时对着计算机屏幕苦苦思索,也曾在春节万家灯火时挑灯奋战。我相信,这些经历终会成为自己技术成长道路上难忘的回忆。

感谢队友的支持、理解,为他们极高的专业水准点赞!也感谢队长和瑜姐提供了本次难得的机会。最后,特别感谢我的女朋友在我翻译工作完成后完美现身,让我可以全身心地投入到翻译工作中。——夏钰辉 花名:漫游鹰

在历时两个月的翻译历程中,有喜有忧,除了看原著撰写译文以外,还有很多团队的沟通工作需要做好,例如,如何让专业名词有统一的翻译、如何跟踪译文的错漏修改、如何与成员讨论细节的处理。在这里,我要感谢广大读者的支持,感谢团队成员的无私包容,也感谢自己内心的那份坚持。——李梓峰 花名:小护士

工作多年来,一直被Java语言的魅力深深吸引。庞大的Java开发生态,可移植、高性能、多线程、动态性、平台无关等优秀特性以及丰富强大的各类开发者社区,让Java语言久盛不衰。出于对Java语言的热爱,我开始一头扎入Java语言特性、多线程编程等许多有趣的领域,进而更加深刻地体会到Java语言的精妙和有趣。

机缘巧合,我有幸参与了这次难得的技术书翻译工作,有幸和这么一群优秀的伙伴一起工作,和大家一起在建设Java社区的道路上尽一份自己的力量。我依旧记得中山公园灯火辉煌的夜晚,记得在屏幕前苦苦思索的自己,也同样记得天南地北的小伙伴们在微信群里讨论如何“信、达、雅”地翻译拗口的语句,又或者讨论各类Java语言的细节。在此过程中,我受益匪浅。回首望去,虽然已经工作许多年了,但依旧不敢言精通任何一样东西。敬畏技术!敬畏人生!学无止境!

深深地感谢一起共事的小伙伴们,感谢成长道路上和我一起前行的亲爱的你们,以及人类科技发展道路上每一个默默付出的人,也诚挚地希望每一个阅读此书的人可以有所收获,有所感悟。——徐江溢 花名:千山

每晚回到家中,关上房门,世界便安静下来;每次台灯下伏案,翻开本书,时间便停止下来。每行文字中的困顿,总在反复琢磨中豁然开朗;每个翻译的字词,总在小心求证中流淌出来。

我欣喜于每一次书中精妙的案例在大脑中演化,召唤我心中的灵感,带给我无限的思考;我沉醉于思考与翻译,希望把这份收获和喜悦传递给读者。

非常荣幸和各位小伙伴一起合作,浮白在春节孜孜不倦,钰辉找到了女朋友……这背后的故事,也让我满心感动。——孙益超 花名:东方ETO翻译小组资源与支持

本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。配套资源

本书将为读者提供源代码。

读者可以在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。提交勘误

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,点击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,将赠与您异步社区的100积分(积分可用于在异步社区兑换优惠券、样书或奖品)。与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。关于异步社区和异步图书“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的标志。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。异步社区微信服务号第1章 线程管理本章内容● 线程的创建、运行和设置● 线程中断● 控制线程中断● 线程的休眠和唤醒● 等待线程执行结束● 守护线程的创建与运行● 处理线程中的不可控异常● 使用线程本地变量● 线程分组及线程组中不可控异常的处理● 使用工厂创建线程1.1 简介

在计算机世界中,并发是指一系列相互无关的任务在一台计算机上同时运行。对于有多个处理器或者多核处理器的计算机来说,这个同时性是真实发生的。然而,对于只有单核处理器的计算机来说,它仅仅是表面现象。

所有现代操作系统均支持并发地执行任务。用户可以在听音乐或者浏览网页的同时阅读邮件。这种并发是进程级别的并发。在同一进程内,也可以有多种同时运行的子任务,我们将这些并发的子任务称为线程。与并发性有关的另一个概念是并行性(parallelism)。虽然它与并发性的概念不同,但是有一定联系。一些学者认为,当多线程应用程序运行在单核处理器上时,程序就是并发运行的;当多线程应用程序运行在多个处理器或者多核处理器上时,程序就是并行运行的。还有一些学者认为,多线程应用程序的线程执行顺序若不是预先定义的,程序就是并发运行的;如果多线程应用程序的线程按照指定顺序执行,那么这个程序就是并行运行的。

本章介绍了如何使用Java 9 API来进行基本的线程操作,包括创建和运行线程、处理线程内抛出的异常、将线程分组,并将分组作为一个整体处理组内的线程。1.2 线程的创建、运行和设置

本节介绍如何使用Java API对线程进行基本的操作。与Java语言中的基本元素一样,线程也是对象(Object)。在Java中,创建线程的方法有以下两种。● 直接继承Thread类,然后重写run()方法。● 构建一个实现Runnable接口的类并重写run()方法,然后创建该

类的实例对象,并以其作为构造参数去创建Thread类的对象。建

议首选这种方法,因为它可以带来更多的扩展性。

在本节中,我们将采用第二种方法创建线程,然后学习如何改变线程的属性。Thread类包含如下一些信息属性,它们能够辅助区分不同的线程、反映线程状态、控制其优先级等。● ID:该属性存储了每个线程的唯一标识符。● Name:该属性存储了线程的名字。● Priority:该属性存储了Thread对象的优先级。在Java 9中,线程

优先级的范围为1~10,其中1表示最低优先级,10表示最高优

先级。通常不建议修改线程的优先级。线程优先级仅供底层操作

系统作为参考,不能保证任何事情,如果一定要修改,请知晓优

先级仅仅代表了一种可能性。● Status:该属性保存了线程的状态。在Java中,线程有6种状态

——Thread.State枚举中定义这些状态:NEW、RUNNABLE、

BLOCKED、WAITING、TIMED_WAITING和TERMINATED。这

些状态的具体意义如下。● NEW:线程已经创建完毕但未开始执行。● RUNNABLE:线程正在JVM中执行。● BLOCKED:线程处于阻塞状态,并且等待获取监视器。● WAITING:线程在等待另一个线程。● TIMED_WAITING:线程等待另一个线程一定的时间。● TERMINATED:线程执行完毕。

本节将在一个案例中创建10个线程来找出20000以内的奇数。项目准备

本案例是用Eclipse IDE实现的。如果开发者使用Eclipse或者其他IDE(例如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据如下步骤实现本案例。

1.创建一个名为Calculator的类,并实现Runnable接口:public class Calculator implements Runnable {

2.实现run()方法。在这个方法中,存放着线程将要运行的指令。在这里,这个方法用来计算20000以内的奇数:@Overridepublic void run() { long current = 1L; long max = 20000L; long numPrimes = 0L; System.out.printf("Thread '%s': START\n", Thread.currentThread().getName()); while (current <= max) { if (isPrime(current)) { numPrimes++; } current++; } System.out.printf("Thread '%s': END. Number of Primes: %d\n", Thread.currentThread().getName(), numPrimes);}

3.实现辅助方法isPrime()。该方法用于判断一个数是否为奇数:private boolean isPrime(long number) { if (number <= 2) { return true; } for (long i = 2; i < number; i++) { if ((number % i) == 0) { return false; } return true; }}

4.实现应用程序的主方法,创建包含main()方法的Main类:public class Main { public static void main(String[] args) {

5.首先,输出线程的最大值、最小值和默认优先级:System.out.printf("Minimum Priority: %s\n", Thread.MIN_PRIORITY);System.out.printf("Normal Priority: %s\n", Thread.NORM_PRIORITY); System.out.printf("Maximun Priority: %s\n", Thread.MAX_PRIORITY);

6.创建10个Thread对象,分别用来执行10个Calculator任务。再创建两个数组,用来保存Thread对象及其State对象。后续我们将用这些信息来查看线程的状态。这里将5个线程设置为最大优先级,另5个线程设置为最小优先级:Thread threads[];Thread.State status[];threads = new Thread[10];status = new Thread.State[10];for (int i = 0; i < 10; i++) {threads[i] = new Thread(new Calculator()); if ((i % 2) == 0) { threads[i].setPriority(Thread.MAX_PRIORITY); } else { threads[i].setPriority(Thread.MIN_PRIORITY); } threads[i].setName("My Thread " + i);}

7.接着将一些必要的信息保存到文件中,因此需要创建try-with-resources语句来管理文件。在这个代码块中,先将线程启动前的状态写入文件,然后启动线程:try (FileWriter file = new FileWriter(".\\data\\log.txt");PrintWriter pw = new PrintWriter(file);) { for (int i = 0; i < 10; i++) { pw.println("Main : Status of Thread " + i + " : " + threads[i].getState()); status[i] = threads[i].getState(); } for (int i = 0; i < 10; i++) { threads[i].start(); }

8.等待线程运行结束。在1.6节中,我们将用join()方法来等待线程结束。本案例中,由于我们需要记录线程运行过程中状态的转变,因此不能使用join()方法来等待线程结束,而应使用如下代码: boolean finish = false; while (!finish) { for (int i = 0; i < 10; i++) { if (threads[i].getState() != status[i]) { writeThreadInfo(pw, threads[i], status[i]); status[i] = threads[i].getState(); } } finish = true; for (int i = 0; i < 10; i++) { finish = finish && (threads[i].getState() == State.TERMINATED); } } } catch (IOException e) { e.printStackTrace(); }}

9.在上述代码中,我们通过调用writeThreadInfo()方法来将线程信息记录到文件中。代码如下:private static void writeThreadInfo(PrintWriter pw, Thread thread, State state) { pw.printf("Main : Id %d - %s\n", thread.getId(), thread.getName()); pw.printf("Main : Priority: %d\n", thread.getPriority()); pw.printf("Main : Old State: %s\n", state); pw.printf("Main : New State: %s\n", thread.getState()); pw.printf("Main : ************************************\n");}

10.运行程序,然后观察不同的线程是如何同时运行的。结果分析

下图是程序在控制台的输出,从中可以看到,线程正在并行处理各自的工作。

从下面的屏幕截图中可以看到线程是如何创建的,拥有高优先级的偶数编号线程比低优先级的奇数编号线程优先执行。该截图来自记录线程状态的log.txt文件。

每个Java应用程序都至少有一个执行线程。在程序启动时,JVM会自动创建执行线程运行程序的main()方法。

当调用Thread对象的start()方法时,JVM才会创建一个执行线程。也就是说,每个Thread对象的start()方法被调用时,才会创建开始执行的线程。

Thread类的属性存储了线程所有的信息。操作系统调度执行器根据线程的优先级,在某个时刻选择一个线程使用CPU,并且根据线程的具体情况来实现线程的状态。

如果没有指定线程的名字,那么JVM会自动按照Thread-XX格式为线程命名,其中XX是一个数字。线程的ID和状态是不可修改的,事实上,Thread类也没有实现setId()和setStatus()方法,因为它们会引入对ID和状态的修改。[1]

一个Java程序将在所有线程完成后结束。初始线程(执行main()方法的线程)完成,其他线程仍会继续执行直到完成。如果一个线程调用System.exit()命令去结束程序,那么所有线程将会终止各自的运行。

创建一个Thread对象并不意味着会创建一个新的执行线程。同样,调用实现Runnable接口类的run()方法也不会创建新的执行线程。只有调用了start()方法,一个新的执行线程才会真正创建。其他说明

正如本节开头所说,还有另一种创建执行线程的方法——实现一个继承Thread的类,并重写其run()方法,创建该类的对象后,调用start()方法即可创建执行线程。

可以使用Thread类的静态方法currentThread()来获取当前运行线程的Thread对象。

调用setPriority()方法时,需要对其抛出的IllegalArgumentException异常进行处理,以防传入的优先级不在合法范围内(1和10之间)。参考阅读● 1.11节1.3 线程中断

一个多线程Java程序,只有当其全部线程执行结束时(更具体地说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),才会结束运行。有时,为了终止程序或者取消一个线程对象所执行的任务,我们需要终止一个线程。

Java使用一种中断机制来向线程表明想要终止它。这个中断机制依靠线程对象来检查是否需要中断,同时线程对象可以决定是否响应中断请求。当然,一个线程对象也可以忽略中断请求继续执行。

本节将开发一个应用程序,它的作用是在线程创建5s后,使用中断机制强制结束线程。项目准备

本案例是用Eclipse IDE 来实现的。如果开发者使用Eclipse 或者其他IDE(例如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据以下步骤来完成本案例。

1.创建一个名为PrimeGenerator的类,并继承Thread类:public class PrimeGenerator extends Thread{

2.重写run()方法——该方法包含一个无限while循环。在循环中,处理从1开始的连续数字。如果是奇数,那么将其输出到控制台:@Overridepublic void run() { long number=1L; while (true) { if (isPrime(number)) { System.out.printf("Number %d is Prime\n",number); }

3.每处理完一个数字,通过isInterrupted()方法来判断当前线程是否已被中断。如果该方法返回true,那么表明当前线程已被中断。在这种情况下,在控制台上打印一条信息并终止线程: if (isInterrupted()) { System.out.printf("The Prime Generator has been Interrupted"); return; } number++; }}

4.实现isPrime()方法。详细代码参见1.2节。

5.现在,开始实现应用程序的主类,创建包含main()方法的Main类:public class Main { public static void main(String[] args) {

6.创建PrimeGenerator类的对象,并启动它:Thread task=new PrimeGenerator();task.start();

7.在主线程中等待5s后,中断PrimeGenerator线程:try { Thread.sleep(5000);} catch (InterruptedException e) { e.printStackTrace();}task.interrupt();

8.输出中断线程的状态。这段代码的输出结果取决于它是在线程结束前还是线程结束后运行的:System.out.printf("Main: Status of the Thread: %s\n",task.getState());System.out.printf("Main: isInterrupted: %s\n",task.isInterrupted());System.out.printf("Main: isAlive: %s\n", task.isAlive());}

9.运行案例并查看结果。结果分析

下面是以上案例运行结果的截图。从图中可以看到,PrimeGenerator线程在检测到自己被中断后,输出信息并结束了运行。

Thread类有一个用来保存线程是否已被中断的状态属性,其属性值为boolean类型,默认值为false。当调用一个线程对象的interrupt()方法时,该状态属性将修改为true。而方法isInterrupted()仅返回该状态属性的值。

在main()方法中,输出了中断线程的一些状态信息。在本案例中,虽然在这些代码之前调用了线程的中断,但是在执行这些代码时,任务线程并未执行到中断判断和处理过程,因此,此时输出的线程状态为RUNNABLE,方法isInterrupted()的结果为true,当然方法isAlive()的结果也为true。如果这些代码执行是在Thread中断完成之后[可以制造机会,如通过在main调用sleep()使得主线程休眠1s,使得task线程完成中断,那么isInterrupted()和isAlive()的结果将为false。其他说明

在Thread类中,还有一个静态方法interrupted(),也能用来检测当前线程是否已被中断。 注意: isInterrupted()方法和interrupted()方法之间有一个重要的不同点:isInterrupted()方法不会修改线程的是否中断属性,而interrupted()方法会将中断属性设置为false。

正如前文所说,线程对象可以忽略中断,但这并不是被预期的行为。1.4 控制线程中断

前面介绍了中断一个线程的方法,以及对线程中断必须要做的处理。尽管之前的案例展示了如何中断一个简单线程,但是当一个线程有划分成多个方法的复杂算法,或者有递归调用时,我们需要更好的机制来控制中断。为此,Java提供了InterruptedException异常,可以在检测到线程中断后抛出该异常,并在run()方法中捕获它。

本节将会实现一个在指定文件夹及其子文件夹下查找文件的线程,并展示如何使用InterruptedException异常来控制线程的中断。项目准备

本案例是用Eclipse IDE实现的。如果开发者使用Eclipse或者其他IDE(例如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据以下步骤完成本案例。

1.创建一个名为FileSearch的类,并实现Runnable接口:public class FileSearch implements Runnable {

2.声明两个私有属性——一个用来存储要搜索的文件名,另一个用来存储要搜索的初始路径。在该类构造器中初始化这两个属性:private String initPath;private String fileName;public FileSearch(String initPath, String fileName) { this.initPath = initPath; this.fileName = fileName; }

3.实现FileSearch类的run()方法。首先检测initPath是否为一个文件夹,如果是,则调用directoryProcess()方法。该方法会抛出InterruptedException异常,因此需要捕获处理:@Overridepublic void run() { File file = new File(initPath); if (file.isDirectory()) { try { directoryProcess(file); } catch (InterruptedException e) { System.out.printf("%s: The search has been interrupted", Thread.currentThread().getName()); } }}

4.实现directoryProcess()方法。这个方法会获取指定文件夹中的文件及其子文件夹,然后处理它们。对于每个子文件夹,该方法会以其作为参数递归调用自己;对于每个文件,该方法将调用fileProcess()方法进行处理。在处理完这些文件和子文件夹以后,该方法将判断线程是否被中断。在本案例中,如果线程被中断,则会抛出InterruptedException异常:private void directoryProcess(File file) throws InterruptedException { File list[] = file.listFiles(); if (list != null) { for (int i = 0; i < list.length; i++) { if (list[i].isDirectory()) { directoryProcess(list[i]); } else { fileProcess(list[i]); } } if (Thread.interrupted()) { throw new InterruptedException(); }}

5.实现fileProcess()方法。该方法将对比文件名是否与所要搜索的文件名相同,如果相同,则向控制台输出信息。完成对比后,该方法会判断线程是否已被中断,在本案例中,如果发生线程中断,则抛出InterruptedException异常:private void fileProcess(File file) throws InterruptedException { if (file.getName().equals(fileName)) { System.out.printf("%s : %s\n", Thread.currentThread().getName(), file.getAbsolutePath()); } if (Thread.interrupted()) { throw new InterruptedException(); }}

6.现在,可以开始实现应用程序的入口,创建包含main()方法的Main类:public class Main { public static void main(String[] args) {

7.创建并初始化一个FileSearch对象,然后用Thread对象启动线程来执行该任务。本案例采用的是Windows路径。在其他操作系统(如Linux或者iOS)下,需要将路径修改为对应系统上一个存在的文件的路径:FileSearch searcher = new FileSearch("C:\\Windows", "explorer.exe");Thread thread=new Thread(searcher);thread.start();

8.等待10s后中断线程: try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt();}

9.运行案例并查看结果。结果分析

案例运行结果如下图所示。可以看到,FileSearch对象在检测到自己被中断时,结束了执行。

本案例使用Java异常来控制线程的中断。在执行案例时,程序通过递归检查文件夹中是否包含指定文件。例如,如果想要进入\b\c\d目录,则程序需要3次递归调用directoryProcess()方法。无论有多少次递归调用,只要它检测到中断,就会立即抛出InterruptedException异常,返回到run()方法中继续执行。其他说明

Java的一些并发API也会抛出InterruptedException异常,如sleep()方法。正如在本案例中,已经休眠的线程被中断(使用interrupted()方法),也抛出了该异常。参考阅读● 1.3节1.5 线程的休眠和唤醒

有时,可能需要在指定的时间段暂停一个线程的执行。例如,一个程序中的某个线程需要每分钟检测一次传感器的状态,其余时间保持空闲。在空闲时间段,线程并不使用任何计算机资源。在空闲时间段之后,该线程由执行调度器选中,继续执行。可以使用Thread类的sleep()方法来达到该目的。该方法接收一个long类型的参数——该参数是线程将要暂停的时长。在暂停时间过后,JVM会重新给该线程分配CPU时间,该线程将继续执行,直到下一个sleep()指令。

还有一种途径,可以使用TimeUnit枚举元素的sleep()方法。该方法调用当前Thread类的sleep()方法,使当前线程进入休眠。但是,其接收的时长参数是以其代表的时间为单位的,其内部实现会在调用线程的方法时自动将该时长转化为毫秒单位的值。

本节将实现一个应用,即使用sleep()方法来打印每一秒的时间。项目准备

本案例是用Eclipse IDE 实现的。如果开发者使用Eclipse或者其他IDE(如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据以下步骤完成本案例。

1.创建一个名为ConsoleClock的类,并实现Runnable接口:public class ConsoleClock implements Runnable {

2.实现run()方法:@Overridepublic void run(){

3.实现一个迭代10次的循环。在每一次迭代中,创建一个Date对象,并将其输出至控制台,然后,调用TimeUnit类SECONDS属性的sleep()方法,使当前线程的执行暂停1s。sleep()方法会抛出InterruptedException异常,因此程序中需要包含处理代码。中断异常的catch部分释放线程使用的资源,这是良好的编程习惯: for (int i = 0; i < 10; i++) { System.out.printf("%s\n", new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { System.out.printf("The FileClock has been interrupted"); } }}

4.在实现线程任务后,开始实现应用程序入口,创建包含main()方法的Main类:public class Main { public static void main(String[] args) {

5.创建一个ConsoleClock类对象和执行该对象的线程,然后开始执行线程:ConsoleClock clock = new ConsoleClock();Thread thread=new Thread(clock);thread.start();

6.调用TimeUnit类SECONDS属性的sleep()方法,使得主线程休眠5s:try { TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) { e.printStackTrace();};

7.中断ConsoleClock线程:thread.interrupt();

8.运行案例并查看结果。结果分析

运行案例后,可以看到程序每秒打印的Date对象以及ConsoleClock线程中断的信息。

当调用sleep()方法时,线程释放CPU资源,停止执行指定的时间。在这段时间里,线程并不消耗CPU时间,因此CPU可以执行其他任务。

当线程在休眠中发生中断时,该方法会立即抛出一个InterruptedException异常,而不会等到休眠时间结束。其他说明

在Java并发API中,还有一个方法可以使线程释放CPU资源,即yield()方法。该方法告知JVM当前线程可以为其他任务放弃CPU资源。JVM并不保证一定会响应该请求。通常只在调试中使用该方法。1.6 等待线程执行结束

在一些场景中,我们必须等待某个线程执行完毕(即run()方法执行结束)。例如,一个程序在必要的资源初始化完毕后,才能开始后续的执行工作。可以将初始化任务作为单独的线程执行,待其结束后再执行其余线程。

使用Thread类的join()方法可以实现这个目的。当调用一个线程对象的join()方法时,发起调用的线程将会暂停,直到线程对象执行结束。

本节将介绍如何在案例初始化过程中使用join()方法。项目准备

本案例是用Eclipse IDE实现的。如果开发者使用Eclipse 或者其他IDE(如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据以下步骤完成本案例。

1.创建一个名为DataSourcesLoader的类,并实现Runnable接口:public class DataSourcesLoader implements Runnable {

2.实现run()方法。向控制台输出执行开始的信息,休眠4s,然后再输出一条执行结束的信息:@Overridepublic void run() { System.out.printf("Beginning data sources loading: %s\n", new Date()); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Data sources loading has finished: %s\n", new Date());}

3.创建一个名为NetworkConnectionsLoader的类,并实现Runnable接口。实现run()方法,它与DataSourcesLoader的run()方法基本一致,但它的休眠时间为6s。

4.实现应用程序入口,创建包含main()方法的Main类:public class Main { public static void main(String[] args) {

5.创建一个DataSourcesLoader类的对象,并创建一个Thread对象来执行该任务:DataSourcesLoader dsLoader = new DataSourcesLoader();Thread thread1 = new Thread(dsLoader,"DataSourceThread");

6.创建一个NetworkConnectionsLoader类的对象,并创建一个Thread对象来执行该任务:NetworkConnectionsLoader ncLoader = new NetworkConnectionsLoader();Thread thread2 = new Thread(ncLoader,"NetworkConnectionLoader");

7.调用两个线程对象的start()方法:thread1.start();thread2.start();

8.用join()方法来等待两个线程执行结束。该方法会抛出InterruptedException异常,因此需要包含捕获代码:try { thread1.join(); thread2.join();} catch (InterruptedException e) { e.printStackTrace();}

9.向控制台输出程序结束信息:System.out.printf("Main: Configuration has been loaded: %s\n", new Date());

10.运行案例并查看结果。结果分析

运行案例可以看到两个线程对象的执行过程。首先,DataSourcesLoader线程结束执行;其次,NetworkConnectionsLoader线程结束执行。然后,主线程才继续执行并输出程序结束信息。其他说明

Java提供了join()方法的另外两个重载版本。● join(long milliseconds)● join(long milliseconds, long nanos)

第一个join()方法的重载版本,不是无限期地等待被调用的线程对象执行完毕,而是最多等待参数中指定的毫秒数。例如,如果在thread1中调用了thread2.join(1000),那么,thread1线程暂停执行,直到遇到以下两个条件之一。● thread2结束执行。● 1000ms的等待时间结束。

只要上述两个条件之一为true,join()方法就会返回。通过线程状态,可以得知join()方法是因为执行结束,还是因为指定时间已到而返回。

第二个join()方法的重载版本与第一个类似,不同之处在于该方[2]法接收毫秒和纳秒作为参数。1.7 守护线程的创建与运行

Java有一种名为守护(daemon)线程的特殊线程。当程序中仅剩守护线程还在运行时,JVM会先结束这些线程然后结束程序。

正是因为这些特性,守护线程通常作为服务提供者,为同一应用内的普通(也称为用户)线程提供服务。守护线程通常包含一个无限循环,来等待一个线程的服务请求或者线程任务。守护线程的典型案例就是Java的垃圾回收器。

本节将通过案例来介绍如何使用守护线程。该案例有两个线程:一个是用户线程,用于向队列写入事件;另一个是守护线程,用于清理队列中超过10s的事件。项目准备

本案例是用Eclipse IDE实现的。如果开发者使用Eclipse或者其他IDE(例如NetBeans),则应打开它并创建一个新的Java项目。案例实现

根据以下步骤完成本案例。

1.创建一个名为Event的类。该类存储了程序中所使用的事件信息。声明两个私有属性:一个是java.util.Date类型的属性date,一个String类型的属性event。生成读写这些属性的方法。

2.创建一个名为WriterTask的类,并实现Runnable接口:public class WriterTask implements Runnable {

3.声明一个用于存储事件的队列,并在类构造器中完成队列初始化:private Deque deque;public WriterTask (Deque deque){ this.deque=deque;}

4.实现该任务类的run()方法。该方法包含100次的迭代循环。在每一次迭代中,我们创建一个新的事件并将其存储到队列当中,然后休眠1s:@Overridepublic void run() { for (int i=1; i<100; i++) { Event event=new Event(); event.setDate(new Date()); event.setEvent(String.format("The thread %s has generated an event", Thread.currentThread().getId())); deque.addFirst(event); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }}

5.创建一个名为CleanerTask的类,并继承Thread类:public class CleanerTask extends Thread {

6.声明一个用于存储事件的队列,并在类构造器中完成队列初始化:private Deque deque;public CleanerTask(Deque deque) { this.deque = deque; setDaemon(true);}

7.实现run()方法。该方法包含一个无限循环,每次循环中,获取当前时间并调用clean()方法:@Overridepublic void run() { while (true) { Date date = new Date(); clean(date); }}

8.实现clean()方法。该方法获取队列中的最后一个事件,如果该事件已创建超过10s,则从队列中删除该事件并检查下一个事件。如果删除了一个事件,则输出该事件信息及队列大小,便于观察执行过程:private void clean(Date date) { long difference; boolean delete; if (deque.size()==0) { return; } delete=false; do { Event e = deque.getLast(); difference = date.getTime() - e.getDate().getTime(); if (difference > 10000) { System.out.printf("Cleaner: %s\n",e.getEvent()); deque.removeLast(); delete=true; } } while (difference > 10000); if (delete){ System.out.printf("Cleaner: Size of the queue: %d\n", deque.size()); }

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载