您好、欢迎来到现金彩票网!
当前位置:红彩会 > 分派优先级 >

线程化和同步

发布时间:2019-07-25 21:00 来源:未知 编辑:admin

  developerWorks 中国 正在向 IBM Developer 过渡。 我们将为您呈现一个全新的界面和更新的主题领域,并一如既往地提供您希望获得的精彩内容。

  线程化和同步是 Java 编程语言的核心特性,Java 语言规范(JLS)中对二者作出了描述。RTSJ 用多种方式扩展了 JLS 的核心功能。(参见参考资料中关于 JLS 和 RTSJ 的链接。)例如,RTSJ 引入了一些新的实时(RT)线程类型,它们必须遵守比普通 Java 线程更加严格的调度策略。另一个例子是优先级继承,它是一种锁定策略,定义了锁竞争时如何管理锁同步。

  理解对优先级和优先级序列的管理有助于理解 RTSJ 针对线程化和同步所作的更改。优先级也是 RT 应用程序使用的一种重要工具。本文通过讨论如何管理线程优先级和优先级序列来描述 RTSJ 线程化和同步。讨论了开发、部署和执行 RT 应用程序(包括使用 IBM WebSphere Real Time 开发的应用程序,参见参考资料)时应该考虑的一些方面。

  JLS 中定义的线程称为普通 Java 线程。普通 Java 线程是类的一个实例,该类拥有从 1 到 10 的 10 个优先级别。为了适应大量的执行平台,JLS 在如何实现、调度和管理普通 Java 线程的优先级方面提供了很大的灵活性。

  Linux 操作系统发展至今已经提供了不同用户级别的线程化实现。Native POSIX Thread Library(NPTL)(参见参考资料)是 Linux 最新版本的战略性线程化实现,由 WebSphere VMs 所使用。NPTL 与它的前任相比优势在于 POSIX 兼容性和性能。在编译时可通过系统的头文件获取 POSIX 服务。可在运行时通过libpthread.so动态库和底层 Linux 核心支持获取 POSIX 服务。Linux 核心可以根据静态控制(如线程优先级级别)和系统中执行的线程的某些动态条件下来执行线程调度。

  POSIX 允许您创建具有不同线程调度策略和优先级的 POSIX 线程(pthreads)以满足不同应用程序的需求。下面是三种此类的调度策略:

  POSIX 通过pthread_mutex数据类型提供锁定和同步支持。pthread_mutex可以使用不同的锁定策略创建。当多个线程需要同时获取同一个锁的时候,锁定策略常常会影响执行行为。标准的 Linux 版本支持单个的默认策略,而 RT Linux 版本还支持优先级继承锁定策略。我们将在本文的同步概述一节对优先级继承策略作更详细的描述。

  在 2.6 Linux 内核中,SCHED_OTHER策略支持 40 个优先级级别。这 40 个优先级级别基于处理器级别来管理,就是说:

  如有需要,Linux 可将线程从一个处理程序迁移到另一个处理程序以平衡工作量。

  在(40 个中的)每个优先级级别中,Linux 管理活动队列和过期队列。每个队列包含一个线程链表(或者为空)。使用活动和过期队列出于以下目的:效率、负载平衡和其他一些目的。逻辑上可将系统看作:为(40 个中的)每个优先级管理一个 FIFO 序列,称为运行队列。一个从非空运行队列的前端分派的线程具有最高的优先级。该线程从队列中移除并执行一段时间(称作:时间量或时间片)。当一个执行线程超过它的时间量时,它的优先级被放在运行队列的后端并给它指定了新的时间量。通过从队列的前端分派线程和在队列的后端放置过期的线程,程序在一个优先级中轮替执行。

  为线程提供的时间量取决于给线程指定的优先级。指定了较高优先级的线程拥有较长的执行时间量。为了防止线程霸占 CPU,Linux 根据一些因素(如线程是 I/O 限制还是 CPU 限制)动态提高或降低线程的优先级。线程可以通过让步(如调用Thread.yield())自愿地放弃它的时间片,或通过阻塞放弃控制权,在阻塞处等待事件发生。释放锁可以触发一个这类的事件。

  WebSphere Real Time 中的 VM 没有显式地指定跨越 40 个SCHED_OTHERLinux 线 个普通 Java 线程优先级。所有的普通 Java 线程,不论其 Java 优先级如何,都被指定为默认的 Linux 优先级。默认的 Linux 优先级处于 40 个SCHED_OTHER优先级的中间位置。通过使用默认优先级,普通 Java 线程可以顺利地执行,即不论 Linux 可能作出何种动态优先级调整,运行队列中的每个普通 Java 线程都能最终得到执行。这里假设的是只执行普通 Java 线程的系统而不是其他系统,比如执行 RT 线程的系统。

  注意:WebSphere Real Time 中的 VM 和 WebSphere VM 的非 RT 版本都使用SCHED_OTHER策略并针对普通 Java 线程使用默认优先级指定。通过使用相同的策略,这两种 JVM 具有相似但不相同的线程调度和同步特征。WebSphere Real Time 类库中的更改、JVM 中的更改和为支持 RTSJ 而在 JIT 编译器中作出的更改,以及 RT Metronome 垃圾收集器的引入(参见参考资料)使应用程序不可能在两种虚拟机中以相同的同步和性能特征运行。在 IBM WebSphere Real Time 测试期间,在测试程序中,同步差异使竞争条件(换言之,bug)浮出了水面,而这些测试程序已经在其他 JVM 上运行了很多年。

  以下各节中的代码示例使用带有让步的 spin 循环。这种方法只适用于演示的目的;不能在 RT 应用程序中使用这种方法。

  清单 1 展示了一个使用普通 Java 线程的程序,确定了两个线程中的每一个在五秒的时间间隔内在一个循环中执行的迭代次数:

  如果此程序在单处理器或卸载的多处理器上运行,则每个线程打印的for循环迭代计数大致相同。在一次运行中,程序将打印:

  如果删除对Thread.yield()的调用,则两个线程的循环计数可能相近,但绝不可能相同。在SCHED_OTHER策略中为这两个线程都指定了相同的默认优先级。因此给两个线程分配了相同的时间片执行。因为线程执行的是相同的代码,所以它们应作出类似的动态优先级调整并在相同的运行队列中轮替执行。但是由于首先执行优先级为 4 的线程,因此在五秒钟的执行时间间隔中,它分得的时间稍多一些并且打印的循环计数也稍大一些。

  RT 线程是javax.realtime.RealtimeThread的一个实例。RTSJ 要求规范的实现必须为 RT 线 个连续的优先级。这些优先级被称作实时优先级。规范中并没有指定 RT 优先级范围的开始值,除非其优先级高于 10 —— 普通 Java 线程的最高优先级值。出于可移植性的原因,应用程序代码应使用新的PriorityScheduler类的getPriorityMin()和getPriorityMax()方法来确定可用的 RT 优先级值的范围。

  JLS 中的线程调度并不精确而且只提供了 10 个优先级值。由 Linux 实现的 POSIXSCHED_OTHER策略满足了各种应用程序的需要。但是SCHED_OTHER策略具有一些不好的特性。动态优先级调整和时间片划分可能在不可预测的时间内发生。SCHED_OTHER优先级的值(40)其实并不算大,其中一部分已经被使用普通 Java 线程的应用程序和动态优先级调整利用了。JVM 还需要对内部线程使用优先级以达到一些特殊目的,比如垃圾收集(GC)。

  缺少确定性、需要更高的优先级级别以及要求与现有应用程序兼容,这些因素引发了对扩展的需求,这将为 Java 程序员提供新的调度功能。RTSJ 中描述的javax.realtime包中的类提供了这些功能。在 WebSphere Real Time 中,LinuxSCHED_FIFO调度策略满足了 RTSJ 调度需求。

  RTSJ 将 RT 优先级视作由运行时系统在逻辑上实现的优先级,该系统为每个 RT 优先级保持一个独立队列。线程调度程序必须从非空的最高优先级队列的头部开始调度。注意:如果所有队列中的线程都不具有 RT 优先级,则调度一个普通 Java 线程按 JLS 中的描述执行(参见普通 Java 线程的线程调度)。

  具有 RT 优先级的调度线程可以一直执行直至阻塞,通过让步自愿放弃控制权,或被具有更高 RT 优先级的线程抢占。具有 RT 优先级并自愿让步的线程的优先级被置于队列的后端。RTSJ 还要求此类调度在不变的时间内进行,并且不能随某些因素变化(如当前执行的 RT 线程的数量)。RTSJ 的 1.02 版本对单处理器系统应用了这些规则;RTSJ 对于多处理器系统上的调度如何运作未作要求。

  Linux 为所有适当的 RTSJ 调度需求提供了SCHED_FIFO策略。SCHED_FIFO策略用于 RT 而不用于用户任务。SCHED_FIFO与SCHED_OTHER策略的区别在于前者提供了 99 个优先级级别。SCHED_FIFO不为线程分时间片。同样,SCHED_FIFO策略也不动态调整 RT 线程的优先级,除非通过优先级继承锁定策略(同步概述一节对此作出了描述)。由于优先级继承的原因,RTSJ 需要使用优先级调整。

  Linux 为 RT 线程和普通 Java 线程提供不变时间调度。在多处理器系统中,Linux 试图模拟分派到可用处理器的单个全局 RT 线程队列的行为。这与 RTSJ 的精神最为接近,但确实与用于普通 Java 线程的SCHED_OTHER策略不同。

  清单 2 中修改后的代码存在一些问题。 如果程序在单处理器环境中运行,则它永远不会结束并且只能打印以下内容:

  出现这样的结果可以用 RT 线程调度的行为来解释。原始线程仍然是一个普通 Java 线程并利用非 RT(SCHED_OTHER)策略运行。只要原始线程启动第一个 RT 线程,RT 线程就抢占原始线程并且 RT 线程将会不确定地运行,因为它不受时间量和线程阻塞的限制。原始线程被抢占后,就再也不允许执行,因此再也不会启动第二个 RT 线程。Thread.yield()对允许原始线程执行反而不起作用 —— 因为让步逻辑将 RT 线程置于其运行队列的末端 —— 但是线程调度程序将再次调度这个线程,因为它是运行队列前端的具有最高优先级的线程。

  允许使用原始线程创建这两个 RT 线程。但是创建第二个线程后,原始线程被抢占并且再也不允许告知线程结束,因为两个 RT 线程在两个处理器上执行而且永远不会阻塞。

  清单 3 显示了修改后能在单处理器系统中正确运行的代码。main()方法的逻辑被移到了一个具有第 8 RT 优先级的 “main” RT 线程中。这个优先级比主 RT 线程创建的两个其他 RT 线程的优先级都要高。拥有最高的 RT 优先级使这个主 RT 线程能够成功地创建两个 RT 线程,并且还允许它从五秒钟的休眠中苏醒时能够抢占当前运行的线. 修改后的 RT 线程示例

  循环的一个迭代。这个输出可通过考虑 RT 线程的优先级来解释。主 RT 线程一直运行,直至调用Thread.sleep()方法来阻塞线程。主 RT 线程创建了两个 RT 线程,但是只有第二个 RT 线 RT 优先级)才能够在主 RT 线程休眠时运行。这个线程一直运行,直至主 RT 线程从休眠中苏醒并指示线程结束。主 RT 线程一旦结束,就允许执行具有第 6 优先级的线程并结束。程序按这种方式执行并打印具有非零值循环计数。此线程结束后,就允许运行具有第 4 RT 优先级的线程,但它只是绕过for循环,因为系统指示结束该线程。该线程将打印零循环计数值然后结束。RT 应用程序的线程化考虑

  )一个AsynchronousEventHandler(AEH)。您还可以定义线程所能够使用的内存类型和数量的限制,如果超过该限制,则抛出OutOfMemoryError。这些工具只对 RT 线程可用,而对普通 Java 线程不可用。您可以在 RTSJ 中找到关于这些工具的更多信息。

  行为。此 API 会像 JLS 中描述的那样中断被阻塞的进程。如果用户在方法声明中加入Throws AsynchronouslyInterruptedException子句,显式地将其标记为可中断,也会引起这个异常。该异常也会困扰线程,用户必须显式地清除异常;否则它会一直困扰(术语为pending)线程。如果用户不清除异常,则线程会伴随着该异常而结束。如果线程以 “常规” 形式结束,但是不是在按自身形式进行 RT 线程入池的应用程序中,这种错误危害不大,就是说,线程返回池中时仍然随附InterruptedException。在这种情况下,执行线程入池的代码应显式地清除异常;否则,当重新分配具有随附异常的入池线程时,可能欺骗性地抛出异常。原始线程和应用程序调度逻辑

  原始线程通常都是普通 Java 线程 —— 而不是 RT 线程。第一个 RT 线程总是由普通 Java 线程创建。如果没有足够的可用处理器来同时运行 RT 线程和普通 Java 线程,则这个 RT 线程会立即抢占普通 Java 线程。抢占可以防止普通 Java 线程继续创建 RT 线程或其他逻辑,以便将应用程序置于适当的初始化状态。

  您可以通过从一个高优先级 RT 线程执行应用程序初始化来避免这个问题。执行自身形式的线程入池和线程调度的应用程序或库可能需要这种技术。即,线程调度逻辑应该以高优先级运行,或在高优先级的线程中运行。为执行线程入池逻辑选择适当的优先级有助于防止线程入队和出队中遇到的问题。

  普通 Java 线程按时间量执行,而动态优先级根据 CPU 的使用调整调度程序的执行,允许所有的普通 Java 线程最后执行。反过来,RT 线程不受时间量的限制,并且线程调度程序不根据 CPU 的使用进行任何形式的动态优先级调整。普通 Java 线程和 RT 线程之间的调度策略差异使失控 RT 线程的出现成为可能。失控 RT 线程可以控制系统并阻止所有其他应用程序的运行,阻止用户登录系统等等。

  在开发和测试期间,有一种技术可以帮助减轻失控线程的影响,即限制进程能够使用的 CPU 数量。在 Linux 上,限制 CPU 的使用使进程在耗尽 CPU 限制时终止失控线程。另外,监控系统状态或提供系统登录的程序应该以高 RT 优先级运行,以便程序可以抢占问题线程。

  策略提供了从 1 到 99 的整数范围内的 99 个 RT 优先级。在这个系统范围内,从 11 到 89 的优先级由 WebSphere VM 使用,此范围的一个子集用来实现 28 个 RTSJ 优先级。28 个 RT Java 优先级映射到此范围的 POSIX 系统优先级,IBM WebSphere Real Time 文档中对这一点作出了描述。但是应用程序代码不应该依赖这个映射,而只应该依赖于 Java 级别的 28 个 RT 优先级的相关顺序。这样 JVM 可以在未来的 WebSphere Real Time 版本中重新映射这个范围并提供改进。如果某些端口监督程序或 RT 进程需要的优先级高于或低于 WebSphere Real Time 中使用的优先级,则应用程序可以使用

  API 将使用 C 代码创建的线程加入到 JVM 中,但 RTSJ 并不对 JNI 接口进行更改或配置以便加入 RT 线程。因此,应用程序应避免用 C 代码创建准备加入到 JVM 中的 POSIX RT 线程。反过来,应该在 Java 语言中创建此类 RT 线程。派生进程和 RT 优先级

  一个线程可以派生另一个进程。在 Linux 上,派生进程的原始线程继承派生它的父线程的优先级。如果派生进程是一个 JVM,则 JVM 的原始线程创建时具有 RT 优先级。这将与普通 Java 线程的顺序冲突,比如原始线程的调度优先级比 RT 线程低。为了防止这种情形,JVM 强制原始线程拥有非 RT 优先级 —— 即拥有

  只让步给具有相同优先级的线程,决不会让步给高于或低于自身优先级的线程。只让步给具有相同优先级的线程意味着在使用多个 RT 优先级的 RT 应用程序中使用Thread.yield()可能会出现问题。应该避免使用Thread.yield(),除非完全有必要。NoHeapRealtimeThreads

  (AEH) 是 RTSJ 附带的新增程序,可将它视为发生事件时执行的一种 RT 线程。例如,可以设置 AEH 在某个特定或关联时间激发。AEH 还具有与 RT 线程相同的调度特征并具有堆和非堆两种风格。同步概述

  —— 运行。要保证程序线程安全地运行,需要序列化访问由多个使用同步原语(如锁或原子机器操作)的线程共享的数据。RT 应用程序的编程人员通常面临使程序按某种时间约束执行的挑战。为了应对这个挑战,他们可能需要了解当前使用组件的实现细节、含意和性能属性。本文的剩余部分将讨论 Java 语言提供的核心同步原语的各个方面,这些原语在 RTSJ 中如何更改,以及 RT 编程人员使用这些原语时需要注意的一些暗示。

  和notify()的线程当前必须已经锁定对象。当线程试图锁定的对象已被其他线程锁定时将发生

  。当发生这种情况时,没有获得锁的线程被置于对象的锁争用者的一个逻辑队列中。类似地,几个线程可能对同一个对象执行Object.wait(),因此该对象拥有一个等待者的逻辑队列。JLS 没有指定如何管理这些队列,但是 RTSJ 规定了这个行为。基于优先级的同步队列

  RTSJ 的原理是所有的线程队列都是 FIFO 并且是基于优先级的。基于优先级的 FIFO 行为 —— 在前面的同步示例中,将接着执行具有最高优先级的线程 —— 也适用于锁争用者和锁等待者的队列。从逻辑观点来看,锁争用者的 FIFO 基于优先级的队列与等待执行的线程执行队列相似。同样有相似的锁等待者队列。

  释放锁以后,系统从争用者的最高优先级队列的前端选择线程,以便试图锁定对象。类似地,完成

  以后,等待者的最高优先级队列前端的线程从等待中解除阻塞。锁释放或锁notify()操作与调度分派操作类似,因为都是对最高优先级队列头部的线程起作用。为了支持基于优先级的同步,需要对 RT Linux 作一些修改。还需要对 WebSphere Real Time 中的 VM 作出更改,以便在执行

  操作时委托 Linux 选择对哪一个线程解除阻塞。优先级反转和优先级继承

  指的是阻塞高优先级线程的锁由低优先级线程持有。中等优先级线程可能抢占低优先级线程,同时持有锁并优先于低优先级线程运行。优先级反转将延迟低优先级线程和高优先级线程的执行。优先级反转导致的延迟可能导致无法满足关键的时限。图 1 的第一条时间线显示这种情况。

  是一种用于避免优先级反转的技术。优先级继承由 RTSJ 规定。优先级继承背后的思想是锁争用,锁持有者的优先级被提高到希望获取锁的线程的优先级。当锁持有者释放锁时,它的优先级则被 “降” 回基本优先级。在刚刚描述的场景中,发生锁争用时低优先级的线程以高优先级运行,直至线程释放锁。锁释放后,高优先级线程锁定对象并继续执行。中等优先级线程禁止延迟高优先级线 中的第二条时间线显示了发生优先级继承时第一条时间线的锁定行为的变化情况。图 1. 优先级反转和优先级继承

  。用于创建pthread_mutex的 POSIX API 使用互斥锁来实现优先级继承协议。有一些 POSIX 服务可用于锁定pthread_mutex和为pthread_mutex解锁。在这些情况下优先级继承支持生效。Linux 在没有锁争用的情况下执行用户空间中的所有锁定。当发生锁争用时,在内核空间中进行优先级提高和同步队列管理。WebSphere VM 使用 POSIX 锁定 API 来实现我们先前所描述的用于支持优先级继承的核心 Java 语言同步原语。用户级别的 C 代码也可以使用这些 POSIX 服务。对于 Java 级别的锁定操作,分配了一个惟一的

  并使用原子机器操作将其绑定到 Java 对象。对于 Java 级别的解锁操作,使用原子操作解除pthread_mutex与对象之间的绑定,前提是不存在锁争用。存在锁争用时,POSIX 锁定和解锁操作将触发 Linux 内核优先级继承支持。为了帮助实现互斥锁分配和锁定时间的最小化,JVM 管理一个全局锁缓存和一个单线程锁缓存,其中每个缓存包含了未分配的

  。线程专用缓存中的互斥锁从全局锁缓存中获得。互斥锁在放入线程锁定缓存之前被线程预先锁定。非争用的解锁操作将一个锁定的互斥锁返回给线程锁定缓存。此处假定以非争用的锁定为标准,而 POSIX 级别的锁定则通过重用预先锁定的互斥锁来得到减少和摊销。JVM 自身拥有内部锁,用于序列化对关键 JVM 资源(如线程列表和全局锁缓存)的访问。这些锁基于优先级继承并且其持有时间较短。

  。如果清单 1中创建的两个线程在持有 RT 线程所需要的锁的时候都不运行,则该程序将不会结束并且出现我们在使用 RT 线程的问题代码示例部分中描述的问题。因为可能出现这种情形,所以对于所有在实时 JVM 中执行的线程来说,执行 spin 循环和让步并不明智。NHRT 和 RT 线程之间的锁争用

  NHRT 可能在 RT 线程(或相应的普通 Java 线程)持有的锁处阻塞。虽然 RT 线程持有锁,但是 GC 可能抢占 RT 并间接地抢占 NHRT。NHRT 需要一直等到 RT 不再被 GC 抢占并释放锁后才能执行。如果 NHRT 执行的功能具有严格的时间要求,则 GC 抢占 NHRT 将是一个严重的问题。

  WebSphere Real Time 中具有确定性的垃圾收集器将暂停时间保持在一毫秒以下,使 NHRT 抢占更具有确定性。如果不能容忍此类暂停,则可以通过避免 NHRT 和 RT 线程之间的锁共享来绕过该问题。如果强制使用锁定,则可以考虑使用特定于 RT 或 NHRT 的资源和锁。例如,实现线程入池的应用程序可以考虑对 NHRT 和 RT 线程使用分开的池和池锁。

  方法故意没有实现同步,因为即使锁是无争用的,同步也会造成系统开销。如果需要同步,则调用方负责封装同步方法或块中所需的javax.realtime方法。编程人员在使用java.realtime包的方法时必须考虑添加此类同步。核心 JLS 包中的同步

  之类的核心 JLS 服务已经实现同步。同样,某些核心 JLS 服务可以执行一些内部锁定来序列化某些共享资源。由于这种同步,在使用核心 JLS 服务时,必须谨慎执行以避免 GC 抢占 NHRT 的问题(参见NHRT 和 RT 线程之间的锁争用)。无争用锁定的性能

  如前所述,无争用锁定操作涉及了一些设置和一个原子机器指令。解锁操作涉及一个原子机器指令。锁定操作的设置涉及分配一个预先锁定的互斥锁。该分配被认为是无争用锁定操作中最大的可变开销。

  。优先级继承可以指提高和降低一组线程的优先级。组中线程的数量应该不大于系统中最深的锁层次结构的深度。通过保持较浅的锁层次结构,可以锁定最少量的对象,您能够影响最大量的需要调整优先级的线程。同步操作中的时间

  包中的新 RT 类和 API 扩展并加强了 Java 编程人员的线程化和同步功能。在 WebSphere Real Time 中,这些功能通过 Linux 内核的 RT 版本来实现,对 POSIX 线程化进行修改并对 JVM 自身进行修改。您现在对 RTSJ 线程化和同步有了更深入的了解,这些知识可以帮助您在编写和部署 RT 应用程序时避免问题的发生。

http://m3-ctech.com/fenpaiyouxianji/670.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有