通过线程门实现Java并发编程(一)

2014-11-23 23:56:52 · 作者: · 浏览: 0

线程门(thread gate)模式是控制线程并发的一个有效工具,但是很多开发者对它并不熟悉。如同交通信号灯可以让汽车在十字路口有序通行一样,线程门可以根据给定条件阻止或允许线程执行。Obi Ezechukwu在本文中为我们介绍了线程门的概念,然后通过一个一个多线程素数生成器告诉我们如何使用它。

  多线程和并发编程曾经是编程高手们才涉足的领域,但是随着多核处理器的出现,以及应用程序需求的更复杂,还有javax.util.concurrent包的出现,这种情况已经发生了变化。现在,企业应用开发者需要了解Java语言中不同的并发机制和构思。当面临需要非教科书式、高度创新的并发构思才能解决的问题时,这种需求更显迫切。这种情形下,仅仅理解Java语言和标准SDK的并发机制已经不能满足需要;你必须能够使用这些工具来编写程序,实现定制化的并发控制。

  在本篇文章中,我们将了解一个人们较少讨论的并发模式,通常被称为线程门。如同现实世界中门的概念一样,门实例可以打开或关闭,从而实现允许或阻止线程执行。它基于某些判定式的真值来实现这个操作。

  下面我将大体介绍一下基于通信流模型的线程门,然后解释一下如何建立示例应用(一个多线程素数生成器)的开发环境。文章的剩余部分将通过实例方式让你了解线程门模式。

  线程门概述

  与线程门非常类似的一个例子是运行在很多十字路口的交通信号灯系统。当红灯亮的时候,汽车必须停车等候,直到信号改变;当绿灯亮的时候,汽车可以自由通行。 交通信号灯被设计用来实现交通的交叉进行,在不需要交叉通行的地方它是没有用武之地的。对于程序员来说,你可以把交通信号灯看作控制器,它可以让双向交通共享使用同一小部分道路,如果没有它,交通线路交叉的地方将是一个非常危险的地方。

  同样,线程门通常最适合的情况是:当一个线程集处于激活状态时,其它线程不能被执行。换句话说,相互竞争的线程集依赖于某些真值判定式的值,判定式的每一个不同的值只触发一个线程集,而强制其它处于挂起状态。注意此处的重点是针对一系列或一组线程而并非单个线程。实际上,我们关注的重点是多个线程共享访问一个底层资源的情形,而且这些线程根据对资源所执行的操作,被划分成不同的集合。

  很好的一个例子是生产者―消费者(producer-consumer)流程,某些线程负责制造另一组线程所使用的数据;共享资源最可能是不同线程集所使用的切换机制(数据总线);而决定线程处理的真值判定式是数据量。如果数据作为一个与生产过程类似的流程部分被进行入队操作,然后该数据被不同过程使用或进行出队操作,一个内存请求队列有时候可能适合此类模式。

  生产者-消费者模型是描述线程门概念使用的一个很好例子。对于绝大多数程序员来说,通过一个示例程序或许能够更轻松理解一个概念。在本篇文章中,问题还应该是如何轻松的实现任务分解和并行操作,因为重点是对其创建一个多线程解决方案。本篇文章的示例应用将实现上述目标
多线程素数生成器

  本篇文章的示例应用程序将解决一个经典老问题,即在指定范围的数字(例如从1到100万之间)找出所有可能的素数。确切的说,我们的任务就是实现一个软件组件,利用一个方法获得指定范围内的所有素数。假定该组件的客户端要求该方法返回一个线程安全的句柄,一旦其被调用的时候可以访问结果,同时完成在后台或以异步方式找出素数的任务。还有一个条件是该操作必须提供一个阻塞方法来允许素数尽快的被访问或返回,这样该客户端就不用必须在访问结果前等待搜索完成。为了简化这个任务,对句柄返回结果的顺序没有添加限制条件。

  在继续阅读本篇文章前,你应该下载本篇文章的代码文件(http://www.javaworld.com/javaworld/jw-03-2009/threadgates-src.zip),然后在自己喜欢使用的IDE中创建一个开发项目。该文件包含十个源文件,其结构如下所示:

1   -src
2
3   -main
4
5   -java
6
7   -com
8
9   -javaworld
10
11   -primefinder
12
13   PrimeNumberSearcher.java
14
15   PrimeNumberSource.java
16
17   PrimeUtil.java
18
19   ThreadGate.java
20
21   PrimeSearchThread.java
22
23   PartitionInfo.java
24
25   PrimeNumberReader.java
26
27   GatedPrimeNumberSearcher.java
28
29   ConcurrentPrimeNumberSource.java
30
31   -src
32
33   -test
34
35   -java
36
37   -com
38
39   -javaworld
40
41   -primefinder
42
43   PrimeFinderTest.java


PrimeFinderTest.java是一个JUnit 4测试包;为了测试验证该示例程序,你需要使用JUnit工具。为了遵循Maven 2命名约定,该应用程序的源文件在src/main/java下,而验证该解决方案的JUnit 4测试包则在src/test/java下。下面我们将对这些文件夹的关键内容进行介绍。
发现素数

  接口com.javaworld.primefinder.PrimeNumberSearcher定义了组件必须遵循的契约。该契约的功能有方法findPrimeNumbers()来指定,如列表1所示。

  列表1.搜索契约

1 PrimeNumberSource findPrimeNumbers(BigInteger lowerBound,
2 BigInteger upperBound);


com.javaworld.primefinder.PrimeNumberSource是结果句柄必须遵循的接口。它定义了一个方法BigInteger nextPrime(),当被调用时,它应该返回搜索结果缓冲器中的下一个元素。如果结果缓冲器已经被用完,必须返回null值来表示没有结果可用。正如此前所提到的,该方法的执行必须是线程安全的。

  需要指出的是,该示例应用程序任务的重点是找出指定搜索范围内的素数;因此我们可以使用来自java.math.BigInteger类的nextProbablePrime()方法。你可以在效用类com.javaworld.primefinder.PrimeUtil的一个静态方法中封装调用该方法,示例程序如列表2所示。

  列表2.在指定范围内发现首个素数的效用方法

1 public static BigInteger findFirstPrime(long lowerBound, long upperBound)
2
3   {
4
5   BigInteger result;
6
7   BigInteger startPos = BigInteger.valueOf(lowerBound);
8
9   BigInteger nextProbablePrime;
10
11   if (startPos.isProbablePrime(.....)) // some reasonable accuracy
12
13   nextProbablePrime = startPos;
14
15   else nextProbablePrime = startPos