当前位置:文档之家› 线程

线程

线程
线程

java.util.concurrent介绍

java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建java.util.concurrent 的目的就是要实现Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。

如果一些类名看起来相似,可能是因为java.util.concurrent 中的许多概念源自Doug Lea 的util.concurrent 库(请参阅参考资料)。

JDK 5.0 中的并发改进可以分为三组:

? JVM 级别更改。大多数现代处理器对并发对某一硬件级别提供支持,通常以

compare-and-swap (CAS)指令形式。CAS 是一种低级别的、细粒度的技术,它允许多个线程更新一个内存位置,同时能够检测其他线程的冲突并进行恢复。它是许多高性能并发算法的基础。在JDK 5.0 之前,Java 语言中用于协调线程之间的访问的惟一原语是同步,同步是更重量级和粗粒度的。公开CAS 可以开发高度可伸缩的并发Java 类。这些更改主要由JDK 库类使用,而不是由开发人员使用。

? 低级实用程序类-- 锁定和原子类。使用CAS 作为并发原语,ReentrantLock 类提供与synchronized 原语相同的锁定和内存语义,然而这样可以更好地控制锁定(如计时的锁定等待、锁定轮询和可中断的锁定等待)和提供更好的可伸缩性(竞争时的高性能)。大多数开发人员将不再直接使用ReentrantLock 类,而是使用在ReentrantLock 类上构建的高级类。

? 高级实用程序类。这些类实现并发构建块,每个计算机科学文本中都会讲述这些类-- 信号、互斥、闩锁、屏障、交换程序、线程池和线程安全集合类等。大部分开发人员都可以在应用程序中用这些类,来替换许多(如果不是全部)同步、wait() 和notify() 的使用,从而提高性能、可读性和正确性。

本教程将重点介绍java.util.concurrent 包提供的高级实用程序类-- 线程安全集合、线程池和同步实用程序。这些是初学者和专家都可以使用的"现成"类。

在第一小节中,我们将回顾并发的基本知识,尽管它不应取代对线程和线程安全的了解。那些一点都不熟悉线程的读者应该先参考一些关于线程的介绍,如"Introduction to Java Threads"教程(请参阅参考资料)。

接下来的几个小节将研究java.util.concurrent 中的高级实用程序类-- 线程安全集合、线程池、信号和同步工具。

最后一小节将介绍java.util.concurrent 中的低级并发构建块,并提供一些性能测评来显示新java.util.concurrent 类的可伸缩性的改进。

什么是线程?

所有重要的操作系统都支持进程的概念-- 独立运行的程序,在某种程度上相互隔离。

线程有时称为轻量级进程。与进程一样,它们拥有通过程序运行的独立的并发路径,并且每个线程都有自己的程序计数器,称为堆栈和本地变量。然而,线程存在于进程中,它们与同一进程内的其他线程共享内存、文件句柄以及每进程状态。

今天,几乎每个操作系统都支持线程,允许执行多个可独立调度的线程,以便共存于一个进程中。因为一个进程中的线程是在同一个地址空间中执行的,所以多个线程可以同时访问相同对象,并且它们从同一堆栈中分配对象。虽然这使线程更易于与其他线程共享信息,但也意味着您必须确保线程之间不相互干涉。

正确使用线程时,线程能带来诸多好处,其中包括更好的资源利用、简化开发、高吞吐量、更易响应的用户界面以及能执行异步处理。

Java 语言包括用于协调线程行为的原语,从而可以在不违反设计原型或者不破坏数据结构的前提下安全地访问和修改共享变量。

线程有哪些功能?

在Java 程序中存在很多理由使用线程,并且不管开发人员知道线程与否,几乎每个Java 应用程序都使用线程。许多J2SE 和J2EE 工具可以创建线程,如RMI、Servlet、Enterprise JavaBeans 组件和Swing GUI 工具包。

使用线程的理由包括:

? 更易响应的用户界面。事件驱动的GUI 工具包(如AWT 或Swing)使用单独的事件线程来处理GUI 事件。从事件线程中调用通过GUI 对象注册的事件监听器。然而,如果事件监听器将执行冗长的任务(如文档拼写检查),那么UI 将出现冻结,因为事件线程直到冗长任务完毕之后才能处理其他事件。通过在单独线程中执行冗长操作,当执行冗长后台任务时,UI 能继续响应。

? 使用多处理器。多处理器(MP)系统变得越来越便宜,并且分布越来越广泛。因为调度的基本单位通常是线程,所以不管有多少处理器可用,一个线程的应用程序一次只能在一个处理器上运行。在设计良好的程序中,通过更好地利用可用的计算机资源,多线程能够提高吞吐量和性能。

? 简化建模。有效使用线程能够使程序编写变得更简单,并易于维护。通过合理使用线程,个别类可以避免一些调度的详细、交叉存取操作、异步IO 和资源等待以及其他复杂问题。相反,它们能专注于域的要求,简化开发并改进可靠性。

? 异步或后台处理。服务器应用程序可以同时服务于许多远程客户机。如果应用程序从socket 中读取数据,并且没有数据可以读取,那么对read() 的调用将被阻塞,直到有数据可读。在单线程应用程序中,这意味着当某一个线程被阻塞时,不仅处理相应请求要延迟,而且处理所有请求也将延迟。然而,如果每个socket 都有自己的IO 线程,那么当一个线程被阻塞时,对其他并发请求行为没有影响。

线程安全

如果将这些类用于多线程环境中,虽然确保这些类的线程安全比较困难,但线程安全却是必需的。java.util.concurrent 规范进程的一个目标就是提供一组线程安全的、高性能的并发构建块,从而使开发人员能够减轻一些编写线程安全类的负担。

线程安全类非常难以明确定义,大多数定义似乎都是完全循环的。快速Google 搜索会显示下列线程安全代码定义的例子,但这些定义(或者更确切地说是描述)通常没什么帮助:

? . . . can be called from multiple programming threads without unwanted interaction between the threads.

? . . . ma y be called by more than on thread at a time without requiring any other action on the caller's part.

通过类似这样的定义,不奇怪我们为什么对线程安全如此迷惑。这些定义几乎就是在说"如果可以从多个线程安全调用类,那么该类就是线程安全的"。这当然是线程安全的解释,但对我们区别线程安全类和不安全类没有什么帮助。我们使用"安全"是为了说明什么?

要成为线程安全的类,首先它必须在单线程环境中正确运行。如果正确实现了类,那么说明它符合规范,对该类的对象的任何顺序的操作(公共字段的读写、公共方法的调用)都不应该使对象处于无效状态;观察将处于无效状态的对象;或违反类的任何变量、前置条件或后置条件。

而且,要成为线程安全的类,在从多个线程访问时,它必须继续正确运行,而不管运行时环境执行那些线程的调度和交叉,且无需对部分调用代码执行任何其他同步。结果是对线程安全对象的操作将用于按固定的整体一致顺序出现所有线程。

如果没有线程之间的某种明确协调,比如锁定,运行时可以随意在需要时在多线程中交叉操作执行。

在JDK 5.0 之前,确保线程安全的主要机制是synchronized 原语。访问共享变量(那些可以由多个线程访问的变量)的线程必须使用同步来协调对共享变量的读写访问。

java.util.concurrent 包提供了一些备用并发原语,以及一组不需要任何其他同步的线程安全实用程序类。

令人厌烦的并发

即使您的程序从没有明确创建线程,也可能会有许多工具或框架代表您创建了线程,这时要求从这些线程调用的类是线程安全的。这样会对开发人员带来较大的设计和实现负担,因为开发线程安全类比开发非线程安全类有更多要注意的事项,且需要更多的分析。

AWT 和Swing

这些GUI 工具包创建了称为时间线程的后台线程,将从该线程调用通过GUI 组件注册的监听器。因此,实现这些监听器的类必须是线程安全的。

TimerTask

JDK 1.3 中引入的TimerTask 工具允许稍后执行任务或计划定期执行任务。在Timer 线程中执行TimerTask 事件,这意味着作为TimerTask 执行的任务必须是线程安全的。

Servlet 和JavaServer Page 技术

Servlet 容器可以创建多个线程,在多个线程中同时调用给定servlet,从而进行多个请求。因此servlet 类必须是线程安全的。

RMI

远程方法调用(remote method invocation,RMI)工具允许调用其他JVM 中运行的操作。实现远程对象最普遍的方法是扩展UnicastRemoteObject。例示UnicastRemoteObject 时,它是通过RMI 调度器注册的,该调度器可能创建一个或多个线程,将在这些线程中执行远程方法。因此,远程类必须是线程安全的。

正如所看到的,即使应用程序没有明确创建线程,也会发生许多可能会从其他线程调用类的情况。幸运的是,java.util.concurrent 中的类可以大大简化编写线程安全类的任务。

例子-- 非线程安全servlet

下列servlet 看起来像无害的留言板servlet,它保存每个来访者的姓名。然而,该servlet 不是线程安全的,而这个servlet 应该是线程安全的。问题在于它使用HashSet 存储来访者的姓名,HashSet 不是线程安全的类。

当我们说这个servlet 不是线程安全的时,是说它所造成的破坏不仅仅是丢失留言板输入。在最坏的情况下,留言板数据结构都可能被破坏并且无法恢复。

public class UnsafeGuestbookServlet extends HttpServlet {

private Set visitorSet = new HashSet();

protected void doGet(HttpServletRequest httpServletRequest,

HttpServletResponse httpServletResponse) throws ServletException, IOException {

String visitorName = httpServletRequest.getParameter("NAME");

if (visitorName != null)

visitorSet.add(visitorName);

}

}

通过将visitorSet 的定义更改为下列代码,可以使该类变为线程安全的:

private Set visitorSet = Collections.synchronizedSet(new HashSet());

如上所示的例子显示线程的内置支持是一把双刃剑-- 虽然它使构建多线程应用程序变得很容易,但它同时要求开发人员更加注意并发问题,甚至在使用留言板servlet 这样普通的东西

时也是如此。

线程安全集合

JDK 1.2 中引入的Collection 框架是一种表示对象集合的高度灵活的框架,它使用基本接口List、Set 和Map。通过JDK 提供每个集合的多次实现(HashMap、Hashtable、TreeMap、WeakHashMap、HashSet、TreeSet、Vector、ArrayList、LinkedList 等等)。其中一些集合已经是线程安全的(Hashtable 和Vector),通过同步的封装工厂

(Collections.synchronizedMap()、synchronizedList() 和synchronizedSet()),其余的集合均可表现为线程安全的。

java.util.concurrent 包添加了多个新的线程安全集合类(ConcurrentHashMap、CopyOnWriteArrayList 和CopyOnWriteArraySet)。这些类的目的是提供高性能、高度

可伸缩性、线程安全的基本集合类型版本。

java.util 中的线程集合仍有一些缺点。例如,在迭代锁定时,通常需要将该锁定保留在集

合中,否则,会有抛出ConcurrentModificationException 的危险。(这个特性有时称为条件线程安全;有关的更多说明,请参阅参考资料。)此外,如果从多个线程频繁地访问集合,则常常不能很好地执行这些类。java.util.concurrent 中的新集合类允许通过在语义中的少量更

改来获得更高的并发。

JDK 5.0 还提供了两个新集合接口-- Queue 和BlockingQueue。Queue 接口与List 类似,但它只允许从后面插入,从前面删除。通过消除List 的随机访问要求,可以创建比现有ArrayList 和LinkedList 实现性能更好的Queue 实现。因为List 的许多应用程序实际上不需要随机访问,所以Queue 通常可以替代List,来获得更好的性能。

弱一致的迭代器

java.util 包中的集合类都返回fail-fast 迭代器,这意味着它们假设线程在集合内容中进行迭代时,集合不会更改它的内容。如果fail-fast 迭代器检测到在迭代过程中进行了更改操作,那么它会抛出ConcurrentModificationException,这是不可控异常。

在迭代过程中不更改集合的要求通常会对许多并发应用程序造成不便。相反,比较好的是它允许并发修改并确保迭代器只要进行合理操作,就可以提供集合的一致视图,如

java.util.concurrent 集合类中的迭代器所做的那样。

java.util.concurrent 集合返回的迭代器称为弱一致的(weakly consistent)迭代器。对于这些类,如果元素自从迭代开始已经删除,且尚未由next() 方法返回,那么它将不返回到调用者。如果元素自迭代开始已经添加,那么它可能返回调用者,也可能不返回。在一次迭代中,无论如何更改底层集合,元素不会被返回两次。

CopyOnWriteArrayList 和CopyOnWriteArraySet

可以用两种方法创建线程安全支持数据的List -- Vector 或封装ArrayList 和Collections.synchronizedList()。java.util.concurrent 包添加了名称繁琐的CopyOnWriteArrayList。为什么我们想要新的线程安全的List类?为什么Vector还不够?

最简单的答案是与迭代和并发修改之间的交互有关。使用Vector 或使用同步的List 封装器,返回的迭代器是fail-fast 的,这意味着如果在迭代过程中任何其他线程修改List,迭代可能失败。

Vector 的非常普遍的应用程序是存储通过组件注册的监听器的列表。当发生适合的事件时,该组件将在监听器的列表中迭代,调用每个监听器。为了防止ConcurrentModificationException,迭代线程必须复制列表或锁定列表,以便进行整体迭代,而这两种情况都需要大量的性能成本。

CopyOnWriteArrayList 类通过每次添加或删除元素时创建支持数组的新副本,避免了这个问题,但是进行中的迭代保持对创建迭代器时的当前副本进行操作。虽然复制也会有一些成本,但是在许多情况下,迭代要比修改多得多,在这些情况下,写入时复制要比其他备用方法具有更好的性能和并发性。

如果应用程序需要Set 语义,而不是List,那么还有一个Set 版本-- CopyOnWriteArraySet。

ConcurrentHashMap

正如已经存在线程安全的List 的实现,您可以用多种方法创建线程安全的、基于hash 的Map -- Hashtable,并使用Collections.synchronizedMap() 封装HashMap。JDK 5.0 添加了ConcurrentHashMap 实现,该实现提供了相同的基本线程安全的Map 功能,但它大大提高了并发性。

Hashtable 和synchronizedMap 所采取的获得同步的简单方法(同步Hashtable 中或者同步的Map 封装器对象中的每个方法)有两个主要的不足。首先,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问hash 表。同时,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。虽然诸如get() 和put() 之类的简单操作可以在不需要额外同步的情况下安全地完成,但还是有一些公用的操作序列,例如迭代或者put-if-absent(空则放入),需要外部的同步,以避免数据争用。

Hashtable 和Collections.synchronizedMap 通过同步每个方法获得线程安全。这意味着当一个线程执行一个Map 方法时,无论其他线程要对Map 进行什么样操作,都不能执行,直到第一个线程结束才可以。

对比来说,ConcurrentHashMap 允许多个读取几乎总是并发执行,读和写操作通常并发执行,多个同时写入经常并发执行。结果是当多个线程需要访问同一Map 时,可以获得更高的并发性。

在大多数情况下,ConcurrentHashMap 是Hashtable或

Collections.synchronizedMap(new HashMap()) 的简单替换。然而,其中有一个显著不同,即ConcurrentHashMap 实例中的同步不锁定映射进行独占使用。实际上,没有办法锁定ConcurrentHashMap 进行独占使用,它被设计用于进行并发访问。为了使集合不被锁定进行独占使用,还提供了公用的混合操作的其他(原子)方法,如put-if-absent。ConcurrentHashMap 返回的迭代器是弱一致的,意味着它们将不抛出ConcurrentModificationException ,将进行"合理操作"来反映迭代过程中其他线程对Map 的修改。

队列

原始集合框架包含三个接口:List、Map 和Set。List 描述了元素的有序集合,支持完全随即访问-- 可以在任何位置添加、提取或删除元素。

LinkedList 类经常用于存储工作元素(等待执行的任务)的列表或队列。然而,List 提供的灵活性比该公用应用程序所需要的多得多,这个应用程序通常在后面插入元素,从前面删除元素。但是要支持完整List 接口则意味着LinkedList 对于这项任务不像原来那样有效。Queue 接口比List 简单得多,仅包含put() 和take() 方法,并允许比LinkedList 更有效的实现。

Queue 接口还允许实现来确定存储元素的顺序。ConcurrentLinkedQueue 类实现先进先出(first-in-first-out,FIFO)队列,而PriorityQueue 类实现优先级队列(也称为堆),它对于构建调度器非常有用,调度器必须按优先级或预期的执行时间执行任务。

interface Queue extends Collection {

boolean offer(E x);

E poll();

E remove() throws NoSuchElementException;

E peek();

E element() throws NoSuchElementException;

}

实现Queue 的类是:

? LinkedList 已经进行了改进来实现Queue。

? PriorityQueue 非线程安全的优先级对列(堆)实现,根据自然顺序或比较器返回元素。

? ConcurrentLinkedQueue 快速、线程安全的、无阻塞FIFO 队列。

任务管理之线程创建

线程最普遍的一个应用程序是创建一个或多个线程,以执行特定类型的任务。Timer 类创建线程来执行TimerTask 对象,Swing 创建线程来处理UI 事件。在这两种情况中,在单独线程中执行的任务都假定是短期的,这些线程是为了处理大量短期任务而存在的。

在其中每种情况中,这些线程一般都有非常简单的结构:

while (true) {

if (no tasks)

wait for a task;

execute the task;

}

通过例示从Thread 获得的对象并调用Thread.start() 方法来创建线程。可以用两种方法创建线程:通过扩展Thread 和覆盖run() 方法,或者通过实现Runnable 接口和使用Thread(Runnable) 构造函数:

class WorkerThread extends Thread {

public void run() { /* do work */ }

}

Thread t = new WorkerThread();

t.start();

或者:

Thread t = new Thread(new Runnable() {

public void run() { /* do work */ }

}

t.start();

重新使用线程

因为多个原因,类似Swing GUI 的框架为事件任务创建单一线程,而不是为每项任务创建新的线程。首先是因为创建线程会有间接成本,所以创建线程来执行简单任务将是一种资源浪费。通过重新使用事件线程来处理多个事件,启动和拆卸成本(随平台而变)会分摊在多个事件上。

Swing 为事件使用单一后台线程的另一个原因是确保事件不会互相干涉,因为直到前一事件结束,下一事件才开始处理。该方法简化了事件处理程序的编写。

使用多个线程,将要做更多的工作来确保一次仅一个线程地执行线程相关的代码。

如何不对任务进行管理

大多数服务器应用程序(如Web 服务器、POP 服务器、数据库服务器或文件服务器)代表远程客户机处理请求,这些客户机通常使用socket 连接到服务器。对于每个请求,通常要进行少量处理(获得该文件的代码块,并将其发送回socket),但是可能会有大量(且不受限制)的客户机请求服务。

用于构建服务器应用程序的简单化模型会为每个请求创建新的线程。下列代码段实现简单的Web 服务器,它接受端口80 的socket 连接,并创建新的线程来处理请求。不幸的是,该代码不是实现Web 服务器的好方法,因为在重负载条件下它将失败,停止整台服务器。

class UnreliableWebServer {

public static void main(String[] args) {

ServerSocket socket = new ServerSocket(80);

while (true) {

final Socket connection = socket.accept();

Runnable r = new Runnable() {

public void run() {

handleRequest(connection);

}

};

// Don't do this!

new Thread(r).start();

}

}

}

当服务器被请求吞没时,UnreliableWebServer 类不能很好地处理这种情况。每次有请求时,就会创建新的类。根据操作系统和可用内存,可以创建的线程数是有限的。

不幸的是,您通常不知道限制是多少-- 只有当应用程序因为OutOfMemoryError 而崩溃时才发现。

如果足够快地在这台服务器上抛出请求的话,最终其中一个线程创建将失败,生成的Error 会关闭整个应用程序。当一次仅能有效支持很少线程时,没有必要创建上千个

线程,无论如何,这样使用资源可能会损害性能。创建线程会使用相当一部分内存,其中包括有两个堆栈(Java 和C),以及每线程数据结构。如果创建过多线程,其中

每个线程都将占用一些CPU 时间,结果将使用许多内存来支持大量线程,每个线程都运行得很慢。这样就无法很好地使用计算资源。

使用线程池解决问题

为任务创建新的线程并不一定不好,但是如果创建任务的频率高,而平均任务持续时间低,我们可以看到每项任务创建一个新的线程将产生性能(如果负载不可预知,还有稳定性)问题。

如果不是每项任务创建一个新的线程,则服务器应用程序必须采取一些方法来限制一次可以处理的请求数。这意味着每次需要启动新的任务时,它不能仅调用下列代码。

new Thread(runnable).start()

管理一大组小任务的标准机制是组合工作队列和线程池。工作队列就是要处理的任务的队列,前面描述的Queue 类完全适合。线程池是线程的集合,每个线程都提取公用工作队列。当一

个工作线程完成任务处理后,它会返回队列,查看是否有其他任务需要处理。如果有,它会转移到下一个任务,并开始处理。

线程池为线程生命周期间接成本问题和资源崩溃问题提供了解决方案。通过对多个任务重新使用线程,创建线程的间接成本将分布到多个任务中。作为一种额外好处,因为请求到达时,线程已经存在,从而可以消除由创建线程引起的延迟。因此,可以立即处理请求,使应用程序更易响应。而且,通过正确调整线程池中的线程数,可以强制超出特定限制的任何请求等待,直到有线程可以处理它,它们等待时所消耗的资源要少于使用额外线程所消耗的资源,这样可以防止资源崩溃。

Executor 框架

java.util.concurrent 包中包含灵活的线程池实现,但是更重要的是,它包含用于管理实现Runnable 的任务的执行的整个框架。该框架称为Executor 框架。

Executor 接口相当简单。它描述将运行Runnable 的对象:

public interface Executor {

void execute(Runnable command);

}

任务运行于哪个线程不是由该接口指定的,这取决于使用的Executor 的实现。它可以运行于后台线程,如Swing 事件线程,或者运行于线程池,或者调用线程,或者新的线程,它甚

至可以运行于其他JVM!通过同步的Executor 接口提交任务,从任务执行策略中删除任务提交。Executor 接口独自关注任务提交-- 这是Executor 实现的选择,确定执行策略。这使在部署时调整执行策略(队列限制、池大小、优先级排列等等)更加容易,更改的代码最少。

java.util.concurrent 中的大多数Executor 实现还实现ExecutorService 接口,这是对Executor 的扩展,它还管理执行服务的生命周期。这使它们更易于管理,并向生命可能比

单独Executor 的生命更长的应用程序提供服务。

public interface ExecutorService extends Executor {

void shutdown();

List shutdownNow();

boolean isShutdown();

boolean isTerminated();

boolean awaitTermination(long timeout,

TimeUnit unit);

// other convenience methods for submitting tasks

}

Executor

java.util.concurrent 包包含多个Executor 实现,每个实现都实现不同的执行策略。什

么是执行策略?执行策略定义何时在哪个线程中运行任务,执行任务可能消耗的资源级别(线程、内存等等),以及如果执行程序超载该怎么办。

执行程序通常通过工厂方法例示,而不是通过构造函数。Executors 类包含用于构造许多不同类型的Executor 实现的静态工厂方法:

? Executors.newCachedThreadPool() 创建不限制大小的线程池,但是当以前创建的线程可以使用时将重新使用那些线程。如果没有现有线程可用,

? 将创建新的线程并将其添加到池中。使用不到60 秒的线程将终止并从缓存中删除。

? Executors.newFixedThreadPool(int n) 创建线程池,其重新使用在不受限制的队列之外运行的固定线程组。在关闭前,所有线程都会因为执行

? 过程中的失败而终止,如果需要执行后续任务,将会有新的线程来代替这些线程。

? Executors.newSingleThreadExecutor() 创建Executor,其使用在不受限制的队列

之外运行的单一工作线程,与Swing 事件线程非常相似。

? 保证顺序执行任务,在任何给定时间,不会有多个任务处于活动状态。

更可靠的Web 服务器-- 使用Executor

前面如何不对任务进行管理中的代码显示了如何不用编写可靠服务器应用程序。幸运的是,修复这个示例非常简单,只需将Thread.start() 调用替换为向Executor 提交任务即可:

class ReliableWebServer {

Executor pool =

Executors.newFixedThreadPool(7);

public static void main(String[] args) {

ServerSocket socket = new ServerSocket(80);

while (true) {

final Socket connection = socket.accept();

Runnable r = new Runnable() {

public void run() {

handleRequest(connection);

}

};

pool.execute(r);

}

}

}

注意,本例与前例之间的区别仅在于Executor 的创建以及如何提交执行的任务。

定制ThreadPoolExecutor

Executors 中的newFixedThreadPool 和newCachedThreadPool 工厂方法返回的Executor 是类ThreadPoolExecutor 的实例,是高度可定制的。

通过使用包含ThreadFactory 变量的工厂方法或构造函数的版本,可以定义池线程的创建。ThreadFactory 是工厂对象,其构造执行程序要使用的新线程。

使用定制的线程工厂,创建的线程可以包含有用的线程名称,并且这些线程是守护线程,属于特定线程组或具有特定优先级。

下面是线程工厂的例子,它创建守护线程,而不是创建用户线程:

public class DaemonThreadFactory implements ThreadFactory {

public Thread newThread(Runnable r) {

Thread thread = new Thread(r);

thread.setDaemon(true);

return thread;

}

}

有时,Executor 不能执行任务,因为它已经关闭或者因为Executor 使用受限制队列存储等待任务,而该队列已满。在这种情况下,需要咨询执行程序的RejectedExecutionHandler 来确定如何处理任务-- 抛出异常(默认情况),放弃任务,在调用者的线程中执行任务,或放弃队列中最早的任务以为新任务腾出空间。

ThreadPoolExecutor.setRejectedExecutionHandler 可以设置拒绝的执行处理程序。

还可以扩展ThreadPoolExecutor,并覆盖方法beforeExecute 和afterExecute,以

添加装置,添加记录,添加计时,重新初始化线程本地变量,或进行其他执行定制。

需要特别考虑的问题

使用Executor 框架会从执行策略中删除任务提交,一般情况下,人们希望这样,那是因为它允许我们灵活地调整执行策略,不必更改许多位置的代码。然而,当提交代码暗含假设特定执行策略时,存在多种情况,在这些情况下,重要的是选择的Executor 实现一致的执行策略。

这类情况中的其中的一种就是一些任务同时等待其他任务完成。在这种情况下,当线程池没有足够的线程时,如果所有当前执行的任务都在等待另一项任务,而该任务因为线程池已满不能执行,那么线程池可能会死锁。

另一种相似的情况是一组线程必须作为共同操作组一起工作。在这种情况下,需要确保线程池能够容纳所有线程。

如果应用程序对特定执行程序进行了特定假设,那么应该在Executor 定义和初始化的附近对这些进行说明,从而使善意的更改不会破坏应用程序的正确功能。

调整线程池

创建Executor 时,人们普遍会问的一个问题是"线程池应该有多大?"。当然,答案取决于硬件和将执行的任务类型(它们是受计算限制或是受IO 的限制?)。

如果线程池太小,资源可能不能被充分利用,在一些任务还在工作队列中等待执行时,可能会有处理器处于闲置状态。

另一方面,如果线程池太大,则将有许多有效线程,因为大量线程或有效任务使用内存,或者因为每项任务要比使用少量线程有更多上下文切换,性能可能会受损。

所以假设为了使处理器得到充分使用,线程池应该有多大?如果知道系统有多少处理器和任务的计算时间和等待时间的近似比率,Amdahl 法则提供很好的近似公式。

用WT 表示每项任务的平均等待时间,ST 表示每项任务的平均服务时间(计算时间)。则WT/ST 是每项任务等待所用时间的百分比。对于N 处理器系统,池中可以近似有

N*(1+WT/ST) 个线程。

好的消息是您不必精确估计WT/ST。"合适的"池大小的范围相当大;只需要避免"过大"和"过小"的极端情况即可。

Future 接口

Future 接口允许表示已经完成的任务、正在执行过程中的任务或者尚未开始执行的任务。通过Future 接口,可以尝试取消尚未完成的任务,查询任务已经完成还是取消了,以及提取(或等待)任务的结果值。

FutureTask 类实现了Future,并包含一些构造函数,允许将Runnable 或Callable(会产生结果的Runnable)和Future 接口封装。因为FutureTask 也实现Runnable,所以可以只将FutureTask 提供给Executor。一些提交方法(如ExecutorService.submit())除了提交任务之外,还将返回Future 接口。

Future.get() 方法检索任务计算的结果(或如果任务完成,但有异常,则抛出ExecutionException)。如果任务尚未完成,那么Future.get() 将被阻塞,直到任务完成;如果任务已经完成,那么它将立即返回结果。

使用Future 构建缓存

该示例代码与java.util.concurrent 中的多个类关联,突出显示了Future 的功能。它实现缓存,使用Future 描述缓存值,该值可能已经计算,或者可能在其他线程中"正在构造"。

它利用ConcurrentHashMap 中的原子putIfAbsent() 方法,确保仅有一个线程试图计算给定关键字的值。如果其他线程随后请求同一关键字的值,它仅能等待(通过Future.get() 的帮助)第一个线程完成。因此两个线程不会计算相同的值。

public class Cache {

ConcurrentMap> map = new ConcurrentHashMap();

Executor executor = Executors.newFixedThreadPool(8);

public V get(final K key) {

FutureTask f = map.get(key);

if (f == null) {

Callable c = new Callable() {

public V call() {

// return value associated with key

}

};

f = new FutureTask(c);

FutureTask old = map.putIfAbsent(key, f);

if (old == null)

executor.execute(f);

else

f = old;

}

return f.get();

}

}

CompletionService

CompletionService 将执行服务与类似Queue 的接口组合,从任务执行中删除任务结果的处理。CompletionService 接口包含用来提交将要执行的任务的submit() 方法和用来询问下一完成任务的take()/poll() 方法。

CompletionService 允许应用程序结构化,使用Producer/Consumer 模式,其中生产者创建任务并提交,消费者请求完成任务的结果并处理这些结果。CompletionService 接口由

ExecutorCompletionService 类实现,该类使用Executor 处理任务并从CompletionService 导出submit/poll/take 方法。

下列代码使用Executor 和CompletionService 来启动许多"solver"任务,并使用第一个生成非空结果的任务的结果,然后取消其余任务:

void solve(Executor e, Collection> solvers)

throws InterruptedException {

CompletionService ecs = new ExecutorCompletionService(e);

int n = solvers.size();

List> futures = new ArrayList>(n);

Result result = null;

try {

for (Callable s : solvers)

futures.add(ecs.submit(s));

for (int i = 0; i < n; ++i) {

try {

Result r = ecs.take().get();

if (r != null) {

result = r;

break;

}

} catch(ExecutionException ignore) {}

}

}

finally {

for (Future f : futures)

f.cancel(true);

}

if (result != null)

use(result);

}

java.util.concurrent 中其他类别的有用的类也是同步工具。这组类相互协作,控制一个或多个线程的执行流。

Semaphore、CyclicBarrier、CountdownLatch 和Exchanger 类都是同步工具的例子。每个类都有线程可以调用的方法,方法是否被阻塞取决于正在使用的特定同步工具的状态和规则。

Semaphore

Semaphore 类实现标准Dijkstra 计数信号。计数信号可以认为具有一定数量的许可权,该许可权可以获得或释放。如果有剩余的许可权,acquire() 方法将成功,否则该方法将被阻塞,直到有可用的许可权(通过其他线程释放许可权)。线程一次可以获得多个许可权。

计数信号可以用于限制有权对资源进行并发访问的线程数。该方法对于实现资源池或限制Web 爬虫(Web crawler)中的输出socket 连接非常有用。

注意信号不跟踪哪个线程拥有多少许可权;这由应用程序来决定,以确保何时线程释放许可权,该信号表示其他线程拥有许可权或者正在释放许可权,以及其他线程知道它的许可权已释放。

互斥

计数信号的一种特殊情况是互斥,或者互斥信号。互斥就是具有单一许可权的计数信号,意味着在给定时间仅一个线程可以具有许可权(也称为二进制信号)。互斥可以用于管理对共享资源的独占访问。

虽然互斥许多地方与锁定一样,但互斥还有一个锁定通常没有的其他功能,就是互斥可以由具有许可权的线程之外的其他线程来释放。这在死锁恢复时会非常有用。

CyclicBarrier 类可以帮助同步,它允许一组线程等待整个线程组到达公共屏障点。CyclicBarrier 是使用整型变量构造的,其确定组中的线程数。当一个线程到达屏障时(通过调用CyclicBarrier.await()),它会被阻塞,直到所有线程都到达屏障,然后在该点允许所有线程继续执行。该操作与许多家庭逛商业街相似-- 每个家庭成员都自己走,并商定1:00 在电

影院集合。当您到电影院但不是所有人都到了时,您会坐下来等其他人到达。然后所有人一起离开。

认为屏障是循环的是因为它可以重新使用;一旦所有线程都已经在屏障处集合并释放,则可以将该屏障重新初始化到它的初始状态。还可以指定在屏障处等待时的超时;如果在该时间内其余线程还没有到达屏障,则认为屏障被打破,所有正在等待的线程会收到BrokenBarrierException。

下列代码将创建CyclicBarrier 并启动一组线程,每个线程将计算问题的一部分,等待所有其他线程结束之后,再检查解决方案是否达成一致。如果不一致,那么每个工作线程将开始另一个迭代。该例将使用CyclicBarrier 变量,它允许注册Runnable,在所有线程到达屏障但还没有释放任何线程时执行Runnable。

class Solver { // Code sketch

void solve(final Problem p, int nThreads) {

final CyclicBarrier barrier =

new CyclicBarrier(nThreads,

new Runnable() {

public void run() { p.checkConvergence(); }}

);

for (int i = 0; i < nThreads; ++i) {

final int id = i;

Runnable worker = new Runnable() {

final Segment segment = p.createSegment(id);

public void run() {

try {

while (!p.converged()) {

segment.update();

barrier.await();

}

}

catch(Exception e) { return; }

}

};

new Thread(worker).start();

}

}

CountdownLatch

CountdownLatch 类与CyclicBarrier 相似,因为它的角色是对已经在它们中间分摊了问题的一组线程进行协调。它也是使用整型变量构造的,指明计数的初始值,但是与CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

其中,CyclicBarrier 是到达屏障的所有线程的大门,只有当所有线程都已经到达屏障或屏障被打破时,才允许这些线程通过,CountdownLatch 将到达和等待功能分离。任何线程都可以通过调用countDown() 减少当前计数,这种不会阻塞线程,而只是减少计数。await() 方法的行为与CyclicBarrier.await() 稍微有所不同,调用await() 任何线程都会被阻塞,直到闩锁计数减少为零,在该点等待的所有线程才被释放,对await() 的后续调用将立即返回。

当问题已经分解为许多部分,每个线程都被分配一部分计算时,CountdownLatch 非常有用。在工作线程结束时,它们将减少计数,协调线程可以在闩锁处等待当前这一批计算结束,然后继续移至下一批计算。

相反地,具有计数1 的CountdownLatch 类可以用作"启动大门",来立即启动一组线程;工作线程可以在闩锁处等待,协调线程减少计数,从而立即释放所有工作线程。下例使用两个CountdownLatche。一个作为启动大门,一个在所有工作线程结束时释放线程:

class Driver { // ...

void main() throws InterruptedException {

CountDownLatch startSignal = new CountDownLatch(1);

CountDownLatch doneSignal = new CountDownLatch(N);

for (int i = 0; i < N; ++i) // create and start threads

new Thread(new Worker(startSignal, doneSignal)).start();

doSomethingElse(); // don't let them run yet

startSignal.countDown(); // let all threads proceed

doSomethingElse();

doneSignal.await(); // wait for all to finish

}

}

class Worker implements Runnable {

private final CountDownLatch startSignal;

private final CountDownLatch doneSignal;

Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {

this.startSignal = startSignal;

this.doneSignal = doneSignal;

}

public void run() {

try {

startSignal.await();

doWork();

doneSignal.countDown();

} catch (InterruptedException ex) {} // return;

}

}

Exchanger 类方便了两个共同操作线程之间的双向交换;这样,就像具有计数为2 的CyclicBarrier,并且两个线程在都到达屏障时可以"交换"一些状态。(Exchanger 模式有时也称为聚集。)

Exchanger 通常用于一个线程填充缓冲(通过读取socket),而另一个线程清空缓冲(通过处理从socket 收到的命令)的情况。当两个线程在屏障处集合时,它们交换缓冲。下列代码说明了这项技术:

class FillAndEmpty {

Exchanger exchanger = new Exchanger();

DataBuffer initialEmptyBuffer = new DataBuffer();

DataBuffer initialFullBuffer = new DataBuffer();

class FillingLoop implements Runnable {

public void run() {

DataBuffer currentBuffer = initialEmptyBuffer;

try {

while (currentBuffer != null) {

addToBuffer(currentBuffer);

if (currentBuffer.full())

currentBuffer = exchanger.exchange(currentBuffer);

}

} catch (InterruptedException ex) { ... handle ... }

}

}

class EmptyingLoop implements Runnable {

public void run() {

DataBuffer currentBuffer = initialFullBuffer;

try {

while (currentBuffer != null) {

takeFromBuffer(currentBuffer);

if (currentBuffer.empty())

currentBuffer = exchanger.exchange(currentBuffer);

}

} catch (InterruptedException ex) { ... handle ...}

}

}

void start() {

new Thread(new FillingLoop()).start();

new Thread(new EmptyingLoop()).start();

}

}

锁定和原子之Lock

Java 语言内置了锁定工具-- synchronized 关键字。当线程获得监视器时(内置锁定),其他线程如果试图获得相同锁定,那么它们将被阻塞,直到第一个线程释放该锁定。同步还确保随后获得相同锁定的线程可以看到之前的线程在具有该锁定时所修改的变量的值,从而确保如果类正确地同步了共享状态的访问权,那么线程将不会看到变量的"失效"值,这是缓存或编译器优化的结果。

虽然同步没有什么问题,但它有一些限制,在一些高级应用程序中会造成不便。Lock 接口将内置监视器锁定的锁定行为普遍化,允许多个锁定实现,同时提供一些内置锁定缺少的功能,如计时的等待、可中断的等待、锁定轮询、每个锁定有多个条件等待集合以及无阻塞结构的锁定。

interface Lock {

void lock();

void lockInterruptibly() throws IE;

boolean tryLock();

boolean tryLock(long time,

TimeUnit unit) throws IE;

void unlock();

Condition newCondition() throws

UnsupportedOperationException;

}

ReentrantLock

ReentrantLock 是具有与隐式监视器锁定(使用synchronized 方法和语句访问)相同的基本行为和语义的Lock 的实现,但它具有扩展的能力。

作为额外收获,在竞争条件下,ReentrantLock 的实现要比现在的synchronized 实现更具有可伸缩性。(有可能在JVM 的将来版本中改进synchronized 的竞争性能。)

这意味着当许多线程都竞争相同锁定时,使用ReentrantLock 的吞吐量通常要比synchronized 好。换句话说,当许多线程试图访问ReentrantLock 保护的共享资源时,JVM 将花费较少的时间来调度线程,而用更多个时间执行线程。

虽然ReentrantLock 类有许多优点,但是与同步相比,它有一个主要缺点-- 它可能忘记释放锁定。建议当获得和释放ReentrantLock 时使用下列结构:

Lock lock = new ReentrantLock();

...

lock.lock();

try {

// perform operations protected by lock

}

catch(Exception ex) {

// restore invariants

}

finally {

lock.unlock();

}

因为锁定失误(忘记释放锁定)的风险,所以对于基本锁定,强烈建议您继续使用synchronized,除非真的需要ReentrantLock 额外的灵活性和可伸缩性。

ReentrantLock 是用于高级应用程序的高级工具-- 有时需要,但有时用原来的方法就很好。

Condition

就像Lock 接口是同步的具体化,Condition 接口是Object 中wait() 和notify() 方法的具体化。Lock 中的一个方法是newCondition(),它要求锁定向该锁定返回新的Condition 对象限制。await()、signal() 和signalAll() 方法类似于wait()、notify() 和notifyAll(),但增加了灵活性,每个Lock 都可以创建多个条件变量。这简化了一些并发算法的实现。

ReadWriteLock

ReentrantLock 实现的锁定规则非常简单-- 每当一个线程具有锁定时,其他线程必须等待,直到该锁定可用。有时,当对数据结构的读取通常多于修改时,可以使用更复杂的称为读写锁定的锁定结构,它允许有多个并发读者,同时还允许一个写入者独占锁定。该方法在一般情况下(只读)提供了更大的并发性,同时在必要时仍提供独占访问的安全性。ReadWriteLock 接口和ReentrantReadWriteLock 类提供这种功能-- 多读者、单写入者锁定规则,可以用这种功能来保护共享的易变资源。

原子变量

Java语言程序设计Java线程作业

选择题: 1、(3分)下列说法中错误的一项是() A、线程创建之后,可以从一个线程组转移到另一个线程组 B、新建的线程默认情况下属于其父线程所属的线程组 C、Java中的第一个线程都属于某个线程组 D、线程只能在其创建时设置所属的线程组 2、(3分)下列说法中错误的一项是() A、线程是一个程序的单个执行流 B、多线程是指一个程序的多个执行流 C、线程就是程序 D、多线程用于实现并发 3、(3分)下列说法中错误的一项是() A、java的线程体由Thread类的run()方法定义 B、java中每一个线程都有自己的名字 C、在程序中通过调用Thread类的run()方法创建线程对象 D、线程创建时已经确定了提供线程体的对象 4、(3分)在以下哪种情况下,线程进入就绪状态?() A、线程调用了notify()方法时 B、线程调用了sleep()方法时 C、线程调用了yield()方法时 D、线程调用了join()方法时 5、(3分)下列说法中错误的一项是() A、所有的对共享数据的访问都是临界区 B、临界区必须使用syschronized标识 C、共享数据的访问不一定全部使用synchronized加锁 D、共享数据的所有访问都必须使用synchronized加锁 6、(3分)下列有关线程的叙述中正确的一项是() A、使用start()方法可以使一个线程成为可运行的,但是它不一定立即开始运行 B、当一个线程因为抢占机制而停止运行时,它被放在可运行队列的前面 C、一旦一个线程被创建,它就立即开始运行

D、一个线程可能因为不同的原因而终止并进入终止状态 7、(3分)下列关于Thread类提供的线程控制方法的说法中,错误的一项是() A、在线程A中执行线程B的join()方法,则线程A等待直到B执行完成 B、currentThread()方法返回当前线程的引用 C、若线程A调用方法isAlive()返回值为true,则说明A正在执行中 D、线程A通过调用interrupt()方法来中断其阻塞状态 8、(3分)下列哪个方法可以使线程从运行状态进入其他阻塞状态() A、start B、wait C、sleep D、yield 9、(3分) 线程调用sleep()方法后,该线程将进入以下哪种状态?() A、死亡状态 B、阻塞状态 C、就绪状态 D、运行状态 10、(3分)Thread类位于下列那个包中?() A、java.io B、java.awt C、java.util D、https://www.doczj.com/doc/0417062671.html,ng 11、(3分)下列说法中错误的一项是() A、新建的线程调用start()方法就能立即进入运行状态 B、线程操作的数据来自Runnable实例 C、线程从传递给Runnable实例run()方法开始执行

Java多线程实现

下面要和大家分享的是Java多线程的实践,其实Java增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活。本文通过一个网络服务器模型,来实践Java5的多线程编程,该模型中使用了Java5中的线程池,阻塞队列,可重入锁等,还实践了Callable,Future 等接口,并使用了Java 的另外一个新特性泛型。 简介 本文将实现一个网络服务器模型,一旦有客户端连接到该服务器,则启动一个新线程为该连接服务,服务内容为往客户端输送一些字符信息。一个典型的网络服务器模型如下: 1. 建立监听端口。 2. 发现有新连接,接受连接,启动线程,执行服务线程。 3. 服务完毕,关闭线程。 这个模型在大部分情况下运行良好,但是需要频繁的处理用户请求而每次请求需要的服务又是简短的时候,系统会将大量的时间花费在线程的创建销毁。Java 5的线程池克服了这些缺点。通过对重用线程来执行多个任务,避免了频繁线程的创建与销毁开销,使得服务器的性能方面得到很大提高。因此,本文的网络服务器模型将如下: 1. 建立监听端口,创建线程池。 2. 发现有新连接,使用线程池来执行服务任务。 3. 服务完毕,释放线程到线程池。 下面详细介绍如何使用Java 5的concurrent包提供的API来实现该服务器。 初始化 初始化包括创建线程池以及初始化监听端口。创建线程池可以通过调用java.util.concurrent.Executors类里的静态方法newChahedThreadPool或是newFixedThreadPool来创建,也可以通过新建一个 java.util.concurrent.ThreadPoolExecutor实例来执行任务。这里我们采用newFixedThreadPool方法来建立线程池。 ExecutorService pool = Executors.newFixedThreadPool(10); 表示新建了一个线程池,线程池里面有10个线程为任务队列服务。 使用ServerSocket对象来初始化监听端口。

托管线程处理

托管线程处理 托管线程处理基本知托管线程处理基本知识识 线程与线程处线程与线程处理理 操作系统使用进程将它们正在执行的不同应用程序分开。线程是操作系统分配处理器时间的基本单元,并且进程中可以有多个线程同时执行代码。每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的 CPU 寄存器组和堆栈。 .NET Framework 将操作系统进程进一步细分为由 System.AppDomain 表示的、称为应用程序域的轻量托管子进程。一个或多个托管线程(由 System.Threading.Thread 表示)可以在同一个托管进程中的一个或任意数目的应用程序域中运行。虽然每个应用程序域都是用单个线程启动的,但该应用程序域中的代码可以创建附加应用程序域和附加线程。其结果是托管线程可以在同一个非托管进程中的应用程序域之间自由移动;您可能只有一个线程在若干应用程序域之间移动。 支持抢先多任务处理的操作系统可以创建多个进程中的多个线程同时执行的效果。它通过以下方式实现这一点:在需要处理器时间的线程之间分割可用处理器时间,并轮流为每个线程分配处理器时间片。当前执行的线程在其时间片结束时被挂起,而另一个线程继续运行。当系统从一个线程切换到另一个线程时,它将保存被抢先的线程的线程上下文,并重新加载线程队列中下一个线程的已保存线程上下文。 时间片的长度取决于操作系统和处理器。由于每个时间片都很小,因此即使只有一个处理器,多个线程看起来似乎也是在同时执行。这实际上就是多处理器系统中发生的情形,在此类系统中,可执行线程分布在多个可用处理器中。 多个线程的优多个线程的优点点 无论如何,要提高对用户的响应速度并且处理所需数据以便几乎同时完成工作,使用多个线程是一种最为强大的技术。在具有一个处理器的计算机上,多个线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。例如,在另一个线程正在重新计算同一应用程序中的电子表格的其他部分时,用户可以编辑该电子表格。 无需修改,同一个应用程序在具有多个处理器的计算机上运行时将极大地满足用户的需要。单个应用程序域可以使用多个线程来完成以下任务: ? 通过网络与 Web 服务器和数据库进行通信。 ? 执行占用大量时间的操作。 ? 区分具有不同优先级的任务。例如,高优先级线程管理时间关键的任务,低优先级线程执行其他任务。 ? 使用户界面可以在将时间分配给后台任务时仍能快速做出响应。 多个线程的缺多个线程的缺点点 建议您使用尽可能少的线程,这样可以最大限度地减少操作系统资源的使用,并可提高性能。线程处理还具有在设计应用程序时要考虑的资源要求和潜在冲突。这些资源要求如下所述: ? 系统将为进程、AppDomain 对象和线程所需的上下文信息使用内存。因此,可以创建的进程、AppDomain 对象和线程的数目会受到可用内存的限制。 ? 跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多数线程都不会产生明显的进度。如果大多数当前线程处于一个进程中,则其他进程中的线程的调度频率就会很低。 ? 使用许多线程控制代码执行非常复杂,并可能产生许多 bug 。 ? 销毁线程需要了解可能发生的问题并对那些问题进行处理。

线程的创建与撤销

师范大学 操作系统(本科) 实验报告 院系:计算机科学技术学院班级: 学生姓名: 学号:20141602141041 指导教师: 教师评阅结果: 教师评语: 实验日期年月日

实验名称: 实验二:线程的创建与撤销 一、实验目的和要求: 熟悉windows系统提供线程的创建与撤销系统调用。 掌握windows系统环境下的线程的创建与撤销方法。 二、实验内容: 使用系统调用createthread()创建一个子线程,并在子线程中显示:thread is runing!,并使用sleep()使线程挂起5s之后使用exitthread(0)撤销线程。 三、实验技术和方法: 1.创建线程 2.撤销线程 3.终止线程 四、实验环境: 使用vc++ 6.0

五、实验步骤和结果:

实验代码: #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////// //////// // The one and only application object CWinApp theApp;

using namespace std; void ThreadName1(); static HANDLE hHandle1=NULL; DWORD dwThreadID1; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; hHandle1=CreateThread((LPSECURITY_ATTRIBUTES) NULL, 0, (LPTHREAD_START_ROUTINE) ThreadName1, (LPVOID) NULL, 0, &dwThreadID1); Sleep(5000); CloseHandle(hHandle1); ExitThread(0); return nRetCode; }

作业3-线程实现加减乘除

电子科技大学 作业报告 学生姓名:学号:指导教师: 学生E-mail: 一、作业名称 线程实现加减乘除服务 二、作业要求 试验要求: 使用多个队列,每一个计算线程有独立的队列用于存储计算请求,请求线程可用一个队列用于接收结果。 只需要实现A(+,-,X,/)B简单两元计算 请求线程与计算线程是多对多关系 三、设计与实现 3.1设计方案 根据试验要求,线程间的通信用队列,所以通信结构就不用考虑了,提到队列,我们就要做初始化队列,朝队列里面添加信息,从队列头部取走信息这些工作,但是在队列的操作中,最重要的就是线程的同步问题了,我选择了一个互斥锁和一个条件变量相结合的方法,互斥锁保证对队列的任何操作必须唯一,条件变量使我们可以放心的等待队列有内容,接着我们就需要选择我们的整体结构了。 最开始,我考虑用一个队列,发送线程把计算式结构体放到这个队列里面,这个计算式结构体里面包括接收线程的ID号,每个接收线程轮流去读取队列,但是只能读取接受进程是自己的结构体,然后再把这些结果都返回给主线程,主

线程对这个计算式进行打印到屏幕,但是这个模型有一定的限制,那就是接受计算线程个数每次去取得互斥锁,并准备去取计算式,发现这个结构体里面的接受线程不是自己,不得不释放互斥锁,轮流这样做,这样的效率会比较低。 由于计算类型是确定的,经过综合考虑,我觉得还是初始化四个发送队列,每个计算线程(加,减,乘,除)各对应一个队列,发送线程会根据自己产生的计算式类型,把这个结构体放入对应的队列中,这样接收线程在队列不空的情况下,就可以去取,并且保证可以取到,同时初始化四个回传队列,计算线程计算完毕后,把结果回传个产生这个计算式的线程。这样虽然会占有一定的空间,但是对于充足的内存,空间换时间的做法还是可以选择的。 在最后选择进程结束时,我考虑的是计算线程不能退出,因为暂时队列为空,不代表没有了该类型的计算式,但是还要保证最后整个线程正常退出,只能让所有的发送线程在接收到各自全部的计算式后返回主线程,这样主线程根据返回的发送线程的个数来判断是否结束整个进程。 另外发送线程的个数以及每个发送线程发送多少个计算式作业,都是用宏定义定义的,只要改变这两个值就可以修改线程的个数和现成的作业数,我这里主要的思想还是想把加减乘除与0,1,2,3对应起来,这样可以保证四个发送队列的类型与加减乘除匹配起来,并且计算线程id数组的下标刚好也用0,1,2,3与加减乘除对应,这样可以简单好理解。 结构图1

程序进程线程并发与异步

实验七程序进程与线程 7.1实验目的 重点理解程序进程与线程的联系与区别,程序运行中的同步与异步概念。掌握Windows平台中的进程对象,进程的属性与参数,编程实现进程启动及其输入输出重定向的实现,程序的同步与异步调用实现。 7.2程序进程与线程 在Windows平台运行程序时首先创建进程描述块(又叫进程控制块),进程控制块包含进程需要的各项资源,进程仅是资源分配的单位。用户编写的程序代码串行存储成磁盘文件,程序运行时系统依据主程序入口创建主线程,工作线程由主线程或其它线程创建,所有线程并发执行,文件中串行的代码经克隆后在机器中并行执行。程序进程与线程的关系由图7-1表示。 图7-1程序进程与线程 Windows平台CPU运行调度的最小单位是线程,所有可运行的线程排成队列,图7-2描绘CPU以时间片轮转的方式执行队列中的线程。

图7-2CPU轮转与线程的并发执行 7.3程序运行中的同步与异步概念 应用程序生成的线程与Windows平台的系统线程是并发执行的,当用户线程调用系统函数时系统线程执行任务并生成结果。用户线程对目标结果有两种依赖方式,要求目标结果必须出现的系统调用即是同步调用,仅有函数调用无须目标结果出现的系统调用即是异步调用。单独线程内的代码或函数是顺次执行前后代码间形成前趋制约,这种前趋制约存在于多个线程间就形成同步运行。多个并发运行的线程如果不存在前趋制约,线程推进速率不互相牵制即形成异步运行,图7-3描绘线程中的同步调用与异步调用。 图7-3系统函数的同步调用与异步调用 .NET框架类中很多的方法同时支持同步与异步方式,比如FileStream类提供对文件流操作,有同步读写操作,也支持异步读写操作。网络通信程序中Socket类即支持同步的Accept 方法,也支持异步方式的BeginAccept方法用于接受一个传入的连接,网络数据接收即具有同步的Receive方法又有异步的BeginReceive方法等。操作系统提供的多种服务调用以驱动线程或系统线程的形式与用户线程异步运行,同步调用与异步调用对用户界面线程影响较大,例如网页浏览器采用的同步方式果获取网页内容,会产生明显的停滞现象,支持异步方式的Ajax技术会更受用户的欢迎,浏览器在获取网页的内容时,部分区域内容的更新不影响其它区域的显

北大操作系统高级课程-陈向群作业-XV6进程线程

阅读代码: 1.基本头文件: types.h param.h memlayout.h defs.h x86.h asm.h mmu.h elf.h 2.进程线程部分: vm.c proc.h proc.c swtch.S kalloc.c 以及相关其他文件代码 强调一下:由于内存管理部分还没有学到,所以请同学们遇到相关的代码和问题时,先将问题记录下来,到学过之后,再结合进程线程管理部分进行深入学习,最后要求对XV6有整体的理解。 请大家围绕如下一些问题阐述原理课的相关内容,以及XV6中是如何实现的。 1.什么是进程,什么是线程?操作系统的资源分配单位和调度单位分别是什么?XV6中的 进程和线程分别是什么,都实现了吗? 答:进程是在多道程序系统出现以后,为了描述系统内部各作业的活动规律而引进的概念。进程有3个基本状态,运行状态、就绪状态和等待状态(或称阻塞状态);进程只能由父进程建立,系统中所有的进程形成一种进程树的层次体系;挂起命令可有进程自己和其他进程发出,但是解除挂起命令只能由其他进程发出。进程是具有独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的独立单位。 线程可称为轻量级的进程,是操作系统可以运行调度的最小单位。线程是进程内的一个相对独立的可执行的单元。若把进程称为任务的话,那么线程则是应用中的一个子任务的执行。 不论操作系统中是否引入了线程,操作系统中资源分配的基本单位都是进程。如果操作系统没有引入线程那么进程就是调度的基本单位。线程并不独立拥有资源,它仅仅分配了一些运行必备的资源。一个进程中的多个线程共同分享进程中的资源。在引入了线程的操作系统中,线程就变成了调度的基本单位,进程中的部分线程阻塞并不代表该线程被阻塞。 xv6操作系统实现了一个基于进程(没有实现线程)的简单进程管理机制。通过对proc.h 文件的阅读了解到xv6的进程中定义了一个context结构,一个枚举类型proc_state定义了UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE 这6种进程的状态,proc结构定义了进程控制块的内容,cpu结构定义了寄存器和栈指针。 2.进程管理的数据结构是什么?在Windows,Linux,XV6中分别叫什么名字?其中包含哪 些内容?操作系统是如何进行管理进程管理数据结构的?它们是如何初始化的? 答:进程管理的数据结构是进程控制块(PCB)。在Linux中进程控制块的结构是由一个叫task_struct的数据结构定义的,ask_struct存在/include/ linux/sched.h中,其中包括管理进程

多线程练习题目

多线程 一、单项 1.下述哪个选项为真?( ) A.Error类是一个RoutimeException异常 B.任何抛出一个RoutimeException异常的语句必须包含在try块之内 C.任何抛出一个Error对象的语句必须包含在try块之内 D. 任何抛出一个Exception异常的语句必须包含在try块之内 2.下列关于Java线程的说法哪些是正确的?( ) A.每一个Java线程可以看成由代码、一个真实的CPU以及数据3部分组成 B.创建线程的两种方法,从Thread类中继承的创建方式可以防止出现多父类问题 C.Thread类属于java.util程序包 D.以上说法无一正确 3.哪个关键字可以对对象加互斥锁?( ) A.transient B.synchronized C.serialize D.static 4.下列哪个方法可用于创建一个可运行的类?() A.public class X implements Runable { public void run() {……} } B. public class X implements Thread { public void run() {……} } C. public class X implements Thread { public int run() {……} } D.public class X implements Runable { protected void run() {……} } 5.下面哪个选项不会直接引起线程停止执行?( ) A.从一个同步语句块中退出来 B.调用一个对象的wait方法 C.调用一个输入流对象的read方法 D.调用一个线程对象的setPriority方法 6.使当前线程进入阻塞状态,直到被唤醒的方法是( ) A.resume()方法 B.wait()方法 C.suspend()方法 D.notify()方法 7.运行下列程序,会产生的结果是( ) public class X extends Thread implements Runnable { public void run(){ System.out.println(“this is run()”); } public static void main(String[] args) { Thread t=new Thread(new X()); t.start(); } }

实验二线程的创建

实验二创建线程 一、实验目的 1. 通过创建线程、观察正在运行的线程和终止线程的程序设计和调试操作,进一步熟悉操作系统的线程概念,理解进程与线程之间的关系。 2. 通过阅读和分析实验程序,学习创建线程、观察线程和终止线程的程序设计方法。 二、实验内容 1. 创建线程 创建线程并因而成就一个多线程程序,是以CreateThread()作为一切行动的开始.此函数的原型如下: HANDLE CreateThread{ LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId}; 如果CreateThread()成功,返回一个新创建的线程的handle。 如果CreateThread()失败,返回一个NULL。可以调用GetLastError()获知原因。

2. 终止线程 线程结束代码可以依靠调用GetExitCodeThread()完成。 BOOL GetExitCodeThread{ HANDLE hThread, /*由CreateThread()传回的线程handle*/ LPDWORD lpExitCode /*指向一个DWORD,用于接受结束代码*/ }; 如果成功,GetExitCodeThread()传回TRUE,否则传回FALSE.如果线程已结束,那么线程的结束代码会被放在lpExitCode参数中带回来.如果线程尚未结束,lpExitCode带回来的值是STILL_ACTIVE。 如果需要用更强制性的手法结束一个线程,可以使用ExitThread()。 三、实验步骤 (1)开启五个线程,设计一个基于Win32多线程应用程序。 (2)基于Win32多线程应用程序,启动两个线程,当用户按下任意键时,试图退出。 (3)验证Thread 使用自己的 Stack 存放 function 中的 local variable。四.程序设计 (1)声明线程标准函数形式,创建等待对象的句柄hThrd,创建接收新线程ID的DWORD变量。进行for循环,执行线程内容ThreadFunc并返回每个核心对象hThrd。之后等待线程全部完成,结束程序。 (2)声明线程标准函数形式,创建等待对象的句柄hThrd1、hThrd2,创建获取线程退出代码的exitCode1、exitCode2,创建接收新线程ID的DWORD变量。执行线程内容ThreadFunc并返回每个核心对象hThrd并输出相关提示信息。进行for循环,接收用户按下的任意键信息,调用GetExitCodeThread等待一个线程的结束,使用GetExitCodeThread传回线程函数ThreadFunc的返回值。函数中用一个死循环,保证两个线程能够完整的运行完成,getch()函数接收用户输入,尝试打断线程,但后面代码保护了线程的继续执行,直至两个线程都执行完成,输出各自的返回值并退出。 (3)验证性程序。

OS中的进程线程同步机制

OS中的进程/线程同步机制 1 常用并发机制 1.1 信号量(Semaphore) 用于进程间传递信号的一个整数值,在信号上只可以进行三种操作,即初始化、递减和递增,这三种操作都是原子操作。递减操作用于阻塞一个进程,递增操作用于解除一个进程的阻塞。信号量也称为计数信号量或一般信号量 1.2 二元信号量(Binary Semaphore) 只取0值和1值的信号量。 1.3 互斥量(Mutex) 类似于二元信号量。关键在于为其加锁(设定值为0)的进程和为其解锁(设定值为1)的进程必须为同一个进程。 1.4 条件变量(Cond) 一种数据类型,用于阻塞进程或线程,直到特定的条件为真。 1.5 管程(Monitor) 一种编程语言结构,它在一个抽象数据类型中封装了变量、访问过程和初始化代码。管程的变量只能由管程自身的访问过程访问,每次只能有一个进程在其中执行,访问过程即临界区。管程可以有一个等待进程队列。 1.6 事件标志(Event Sign) 用作同步机制的一个内存字。应用程序代码可为标志中的每个位关联不同的事件。通过测试相关的一个或多个位,线程可以等待一个或多个事件。在全部所需位都被设定(AND)或至少一个位被设定(OR)之前,线程会一直被阻塞。 1.7 信箱/消息(Mailbox) 两个进程间交换信息的一种方法,也可用于同步。 1.8 自旋锁(Spin Lock) 一种互斥机制,进程在一个无条件循环中执行,等待锁变量的值可用。

2 常用进程/线程同步机制介绍 2.1 Windows OS中常用进程/线程同步机制 2.1.1 临界区(Critical Section) 可用于进程和线程同步。 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 临界区包含两个操作原语: EnterCriticalSection()进入临界区 LeaveCriticalSection()离开临界区 EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。 MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。 2.1.2 互斥量(Mutex) 进程和线程都可用的一种同步机制。互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。 互斥量包含的几个操作原语: CreateMutex()创建一个互斥量 OpenMutex()打开一个互斥量 ReleaseMutex()释放互斥量 WaitForMultipleObjects()等待互斥量对象 2.1.3 信号量(Semaphore) 进程和线程都可用的同步机制。 信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一

java线程练习题及答案

线程与线程类 1 线程的概念 线程的概念来源于计算机的操作系统的进程的概念。进程是一个程序关于某个数据集的一次运行。也就是说,进程是运行中的程序,是程序的一次运行活动。 线程和进程的相似之处在于,线程和运行的程序都是单个顺序控制流。有些教材将线程称为轻量级进程(light weight process)。线程被看作是轻量级进程是因为它运行在一个程序的上下文内,并利用分配给程序的资源和环境。 作为单个顺序控制流,线程必须在运行的程序中得到自己运行的资源,如必须有自己的执行栈和程序计数器。线程内运行的代码只能在该上下文内。因此还有些教程将执行上下文(execution context)作为线程的同义词。 所有的程序员都熟悉顺序程序的编写,如我们编写的名称排序和求素数的程序就是顺序程序。顺序程序都有开始、执行序列和结束,在程序执行的任何时刻,只有一个执行点。线程(thread )则是进程中的一个单个的顺序控制流。单线程的概念很简单,如图1所示。 多线程(multi-thread )是指在单个的程序内可以同时运行多个不同的线程完成不同的任务,图2说明了一个程序中同时有两个线程运行。 图1 单线程程序示意图 图2 多线程程序示意图 有些程序中需要多个控制流并行执行。例如, for(int i = 0; i < 100; i++) System.out.println("Runner A = " + i); for(int j = 0; j < 100; j++ ) System.out.println("Runner B = "+j); 上面的代码段中,在只支持单线程的语言中,前一个循环不执行完不可能执行第二个循环。要使两个循环同时执行,需要编写多线程的程序。 很多应用程序是用多线程实现的,如Hot Java Web 浏览器就是多线程应用的例子。在Hot Java 浏览器中,你可以一边滚动屏幕,一边下载Applet 或图像,可以同时播放动画和声音等。 2 Thread 类和Runnable 接口 多线程是一个程序中可以有多段代码同时运行,那么这些代码写在哪里,如何创建线程对象呢? 首先,我们来看Java 语言实现多线程编程的类和接口。在https://www.doczj.com/doc/0417062671.html,ng 包中定义了Runnable 接口和Thread 类。

Spring提供的线程池支持

核心提示:一旦企业应用越来越复杂时(比如,基于流程服务器的EIS),它们对相关技术也提出 了更高的要求。在使用EJB 3.0组件技术开发企业应用过程中,它们能够享受到EJB容器提供的线 程池、任务调度(@Timeout)服务。现如今,运行于Web容器的Web应用、单独的桌面应用 一旦企业应用越来越复杂时(比如,基于流程服务器的EIS),它们对相关技术也提出了更高的要求。在使用EJB 3.0组件技术开发企业应用过程中,它们能够享受到EJB容器提供的线程池、任务调度(@Timeout)服务。现如今,运行于Web容器的Web应用、单独的桌面应用也复杂到需要依赖于线程池、任务调度的这类服务,是时候实现贵族到平民的转变了。 过去,很多企业项目可能会自身实现这类底层的非功能性的服务。从Java SE 5.0开始,线程池服务(即,java.util.concurrent包)已经内置到JDK中。至于任务调度服务,其实,自从Java 2 SDK 1.3以来,Java 2就内置了用于任务调度的定时器(比如,java.util.Timer、javax.swing.Timer)。开源领域的Quartz Scheduler正是能够提供企业级任务调度服务的使能技术,而且EJB 2.x也加强了任务调度的支持。甚至,BEA同IBM合作开发了Timer and Work Manager for Application Servers技术规范(CommonJ),这是专门用来解决Java EE应用中的线程池、任务调度问题的。另外,JMX规范中也定义了javax.management.timer.TimerMBean,开发者借助于它能够实现定时JMX通知。 针对上述各种线程池、任务调度支持,Spring 2.0提供了统一的客户视图、抽象,这使得应用根本不用理会底层的具体实现和机制。本章将从分析Spring 2.0提供的线程池支持入手,并过渡到Spring 2.0对任务调度提供的支持当中,从而进入到下一章内容。 15.1 Spring提供的线程池支持 自从Spring 2.0开始,TaskExecutor接口被引入到Spring平台中,这主要受到Java SE 5.0中java.util.concurrent.Executor的影响。这一接口为各种线程池服务提供了抽象,它在统一客户视图 方面起到了最重要的作用。无论是Spring 2.0内部实现中,还是各种基于Spring的企业应用,TaskExecutor的应用随处可见,其定义如下。 public interface TaskExecutor { //异步或同步执行用户提交的任务 void execute(Runnable task); } 开发者可以通过execute(Runnable task)方法将待执行的任务提交给TaskExecutor。依据不同的TaskExecutor接口实现,这一任务会以异步或同步的方式进行。如果是同步,则调用者一直处于阻 塞状态,直到任务被执行完成。此时,调用者同目标任务的执行处于同一线程中,因此线程上下 文信息能够传播到目标任务的执行过程中。如果是异步,则一旦提交完任务,调用者即可返回, 并继续进行自身的其他操作。此时,调用者同目标任务的执行位于不同的线程中,因此线程上下 文信息很可能不能够在它们之间共享。应用要合理选择同步或异步。比如,在调用execute()期间, 如果采用Spring受管事务或Acegi提供的企业级安全性服务,则一旦同步或异步选用不当,事务

C++多线程编程入门及范例详解

多线程编程之一——问题提出 一、问题的提出 编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG 添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,代码如下: 1.void CSingleThreadDlg::OnSleepSixSecond() 2.{ 3.Sleep(6000);//延时6秒 4.} 编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种耗时的操作,我们有必要学习——多线程编程。 二、多线程概述 进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。 线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows 系统。主执行线程终止了,进程也就随之终止。 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。 多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。 Win32SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。 三、Win32API对多线程编程的支持 Win32提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。

作业一

《嵌入式系统原理》作业一 注意: ●上交时间:下周上小课之前; ●不许打印答案,可以不抄题目,把答写在作业纸上就可以了; 一、填空题 1、ARM 机-机器),CM4处理器内部正在执行的指令的地址为0x08000100,此时读取 PC 2、CM4 4、根据操作数的来源不同,CM4处理器有不同的寻址方式,最快的寻址方式是 add r0,r1,r2 5、ATPCS中规定子程序间传递参数,参数个数少于等于4个时,使用寄存器 4 6、在CM4处理器中,寄存器和内存单元 7、伪操作import 伪操作“ldr r0,=label” 8、假设某嵌入式处理器有5级流水线,每级流水线所耗时间均为为0.2ms,则顺序执行30条指令需要耗费时间是 6.8 ms。 二、编程题 1、编写一段c语言和汇编语言的混合程序,在c语言函数中实现字符串拷贝(不许用字符串操作相关函数),函数原型如下:

void strcpy(char *src, char *dest) //即src所指向的字符串复制到dest中{ // 自己编写字符串拷贝代码 While(*src!=0) { *dest++=*src++ } } 然后,在汇编语言程序通过调用strcpy完成字符串src到dest的复制! PRESERVE8 THUMB AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0x20004000 ; Top of Stack DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY Reset_Handler PROC EXPORT Reset_Handler [WEAK] ……;此处需将汇编代码补充完整 ldr r0,=src ldr r1,=0x20001000 Import strcpy Bl strcpy src dcb “Where is a will,where is a way!”,0 dest space 50 end

四种进程或线程同步互斥的控制方法

四种进程或线程同步互斥的控制方法 1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 2、互斥量:为协调共同对一个共享资源的单独访问而设计的。 3、信号量:为控制一个具有有限数量用户资源而设计。 4、事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。 一临界区 临界区的使用在线程同步中应该算是比较简单,说它简单还是说它同后面讲到的其它方法相比更容易理解。举个简单的例子:比如说有一个全局变量(公共资源)两个线程都会对它进行写操作和读操作,如果我们在这里不加以控制,会产生意想不到的结果。假设线程A 正在把全局变量加1然后打印在屏幕上,但是这时切换到线程B,线程B又把全局变量加1然后又切换到线程A,这时候线程A打印的结果就不是程序想要的结果,也就产生了错误。解决的办法就是设置一个区域,让线程A在操纵全局变量的时候进行加锁,线程B如果想操纵这个全局变量就要等待线程A释放这个锁,这个也就是临界区的概念。 二互斥体 windows api中提供了一个互斥体,功能上要比临界区强大。也许你要问,这个东东和临界区有什么区别,为什么强大?它们有以下几点不一致: 1.critical section是局部对象,而mutex是核心对象。因此像waitforsingleobject是不可以等待临界区的。 2.critical section是快速高效的,而mutex同其相比要慢很多 3.critical section使用围是单一进程中的各个线程,而mutex由于可以有一个名字,因此它是可以应用于不同的进程,当然也可以应用于同一个进程中的不同线程。 4.critical section 无法检测到是否被某一个线程释放,而mutex在某一个线程结束之后会产生一个abandoned的信息。同时mutex只能被拥有它的线程释放。下面举两个应用mutex 的例子,一个是程序只能运行一个实例,也就是说同一个程序如果已经运行了,就不能再运行了;另一个是关于非常经典的哲学家吃饭问题的例子。 三事件 事件对象的特点是它可以应用在重叠I/O(overlapped I/0)上,比如说socket编程中有两种模型,一种是重叠I/0,一种是完成端口都是可以使用事件同步。它也是核心对象,因此可以被waitforsingleobje这些函数等待;事件可以有名字,因此可以被其他进程开启。 四信号量 semaphore的概念理解起来可能要比mutex还难,我先简单说一下创建信号量的函数,因为我在开始使用的时候没有很快弄清楚,可能现在还有理解不对的地方,如果有错误还是请大侠多多指教。 CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD LONG lInitialCount, // initial count LONG lMaximumCount, // maximum count LPCTSTR lpName // object name )

最新java多线程试题-答案

多线程 一.选择题 1.下列说法中错误的一项是(A) A.线程就是程序 B.线程是一个程序的单个执行流 B.多线程是指一个程序的多个执行流D.多线程用于实现并发 2.下列哪个一个操作不能使线程从等待阻塞状态进入对象阻塞状态(D) A.等待阴塞状态下的线程被notify()唤 B.等待阻塞状态下的纯种被interrput()中断 C.等待时间到 D.等待阻塞状态下的线程调用wait()方法 3.下列哪个方法可以使线程从运行状态进入其他阻塞状态(A) A.sleep B.wait C.yield D.start 4.下列说法中错误的一项是(D) A.一个线程是一个Thread类的实例 B.线程从传递给纯种的Runnable实例run()方法开始执行 C.线程操作的数据来自Runnable实例 D.新建的线程调用start()方法就能立即进入运行状态 5.下列关于Thread类提供的线程控制方法的说法中,错误的一项是(D) A.在线程A中执行线程B的join()方法,则线程A等待直到B执行完成 B.线程A通过调用interrupt()方法来中断其阻塞状态 C.若线程A调用方法isAlive()返回值为true,则说明A正在执行中 D.currentThread()方法返回当前线程的引用 6.下列说法中,错误的一项是() A.对象锁在synchronized()语句执行完之后由持有它的线程返还 B.对象锁在synchronized()语句中出现异常时由持有它的线程返还 C.当持有锁的线程调用了该对象的wait()方法时,线程将释放其持有的锁 D.当持有锁的线程调用了该对象的构造方法时,线程将释放其持有的锁 7.下面的哪一个关键字通常用来对对象的加锁,从而使得对对象的访问是排他的 A A.sirialize B transient C synchronized D static 二.填空题 1. 在操作系统中,被称做轻型的进程是线程 2. 多线程程序设计的含义是可以将一个程序任务分成几个并行的任务 3. 在Java程序中,run()方法的实现有两种方式:实现Runnable接口和继承Thread类 4 .多个线程并发执行时,各个线程中语句的执行顺序是确定的,但是线程之间的相对执行顺序是不确定的 6.Java中的对象锁是一种独占的排他锁 7.程序中可能出现一种情况:多个线种互相等待对方持有的锁,而在得到对方的锁之前都不会释放自己的锁,这就是死锁 8.线程的优先级是在Thread类的常数MIN_PRIORITY 和MAX_PRIORITY 之间的一个值 9.处于新建状态的线程可以使用的控制方法是start() 和stop() 。 10.一个进程可以包含多个线程

相关主题
文本预览
相关文档 最新文档