线程的同步和异步_线程和同步

线程和同步是Java编程语言的核心功能,在Java语言规范(JLS)中进行了描述。 RTSJ通过多种方式扩展了JLS的核心功能。 (请参阅相关主题的链接到JLS和RTSJ。)例如,必须遵守比普通Java线程更加严格的调度策略的RTSJ引入了新的实时(RT)线程类型。 另一个示例是优先级继承 ,这是一种锁定策略,定义了争用锁时如何管理锁同步。

了解优先级和优先级队列的管理对于了解RTSJ对线程和同步的更改很有用。 优先级也是RT应用程序使用的重要工具。 本文通过讨论如何管理线程优先级和优先级队列来描述RTSJ线程和同步的各个方面。 讨论包括在开发,部署和执行RT应用程序时应考虑的考虑因素,包括使用IBMWebSphere®Real Time构建的应用程序(请参阅参考资料 )。

了解常规Java线程

JLS中定义的线程称为常规Java线程 。 常规Java线程是java.lang.Thread类的实例,该实例的整数优先级在1到10的范围内。为了适应多种执行平台,JLS在实现,安排,并管理常规Java线程的优先级。

Linux®上的WebSphere VM(包括WebSphere Real Time)使用Linux操作系统提供的本机线程服务。 通过了解Linux线程和同步的各个方面,您可以了解Java线程和同步的各个方面。

Linux线程和同步

Linux操作系统在其整个历史中一直提供不同的用户级线程实现。 本地POSIX线程库(NPTL)(请参阅参考资料 )是Linux的最新战略性线程指导,也是WebSphere VM使用的一种。 与NPTL相比,NPTL的优点是符合POSIX和更好的性能。 POSIX服务可在编译时通过系统头文件使用。 POSIX服务可在运行时通过libpthread.so动态库和基础Linux内核支持获得。 Linux内核基于静态控件(例如线程优先级)以及系统中执行的线程的某些动态条件来执行线程调度。

POSIX允许您使用不同的线程调度策略和优先级创建POSIX线程( pthreads ),以满足不同的应用程序需求。 三种这样的调度策略是:

  • SCHED_OTHER
  • SCHED_FIFO
  • SCHED_RR

SCHED_OTHER策略用于传统的用户任务,例如程序开发工具,办公应用程序和Web浏览器。 SCHED_RRSCHED_FIFO旨在供需要更多确定性和及时性需求的应用程序使用。 之间的主要区别SCHED_RRSCHED_FIFOSCHED_RR 时间片线程的执行,而SCHED_FIFO没有。 SCHED_OTHERSCHED_FIFO策略由WebSphere Real Time使用,下面将对其进行详细描述。 (我们不介绍SCHED_RR策略,WebSphere Real Time不使用该策略。)

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

Linux调度和锁定涉及管理先进先出(FIFO)队列。

使用常规Java线程进行线程调度

RTSJ指示常规Java线程的行为与JLS中定义的行为相同。 在WebSphere Real Time中,常规Java线程是使用Linux的POSIX SCHED_OTHER调度策略实现的。 SCHED_OTHER策略适用于诸如编译器和文字处理器之SCHED_OTHER的应用程序,而不适用于需要更多确定性的任务。

在2.6 Linux内核中, SCHED_OTHER策略支持40个优先级。 这40个优先级是在每个处理器的基础上进行管理的,这意味着:

  • 由于高速缓存性能的原因,Linux尝试在同一处理器上执行线程。
  • 线程调度主要使用每个处理器锁,而不是每个系统锁。

如果需要,Linux会将线程从一个处理器迁移到另一个处理器以平衡工作负载。

在40个优先级中,Linux管理一个活动队列和一个到期队列 。 每个队列包含一个线程链接列表,或者为空。 活动队列和到期队列用于提高效率,负载平衡和其他目的。 从逻辑上讲,您可以将系统视为管理40个优先级中的每个优先级的单个FIFO队列(称为运行队列) 。 从非空运行队列的前端调度具有最高优先级的线程。 该线程将从队列中删除,并执行一段时间,称为时间量或时间片 。 当执行线程的时间量到期时,它将按优先级放在运行队列的末尾,并分配一个新的时间量。 通过从队列的头部分派并将过期的线程放在队列的尾部,执行将在优先级内以循环方式进行。

分配给线程的时间量取决于线程分配的优先级。 具有较高分配优先级的线程将获得更长的执行时间。 为了防止线程占用CPU,Linux根据诸如线程是I / O还是CPU绑定等因素动态地提高或降低线程的优先级。 线程可以通过让步(例如通过调用Thread.yield() )来自愿放弃其时间片,或者线程可以通过阻塞来放弃控制,此时线程等待事件发生。 通过释放锁可以触发这样的事件。

WebSphere Real Time中的VM不会在40个SCHED_OTHER Linux线程优先级的范围内显式分配10个常规Java线程优先级。 所有常规Java线程(无论其Java优先级如何)均被分配为默认Linux优先级。 默认的Linux优先级在40个SCHED_OTHER优先级范围的中间。 通过使用默认值,常规Java线程可以公平地执行,即无论Linux可以进行动态优先级调整如何,运行队列中的每个常规Java线程最终都将执行。 假设系统仅执行常规Java线程,而没有执行RT线程的系统。

请注意,WebSphere Real Time中的VM和WebSphere VM的非RT版本均使用SCHED_OTHER策略和常规Java线程的默认优先级分配。 通过使用相同的策略,两个JVM具有相似但不相同的线程调度和同步特性。 为了支持RTSJ,WebSphere Real Time类库的变化,JVM的变化以及JIT编译器的变化以及RT Metronome垃圾收集器的引入(请参阅参考资料 )使应用程序无法以相同的方式运行两个JVM中的时序和性能特征。 在IBM WebSphere Real Time测试期间,计时差异会在已经在其他JVM上运行多年的测试程序中浮现出竞争条件(换句话说,错误)。

使用常规Java线程的代码示例

清单1显示了一个使用常规Java线程的程序,该程序确定了两个创建的线程在五秒钟的间隔内可以执行多少次循环迭代:

清单1.常规Java线程
class myThreadClass extends java.lang.Thread {
   volatile static boolean Stop = false;

   // Primordial thread executes main()
   public static void main(String args[]) throws InterruptedException {

      // Create and start 2 threads
      myThreadClass thread1 = new myThreadClass();
      thread1.setPriority(4);    // 1st thread at 4th non-RT priority
      myThreadClass thread2 = new myThreadClass();
      thread2.setPriority(6);    // 2nd thread at 6th non-RT priority
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      Thread.sleep(5*1000);
      Stop = true;
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

清单1中的程序具有三个用户线程,它们是常规的Java线程:

  • 原始线程:
    • 它是在进程启动时隐式创建并执行main()方法的主线程。
    • main()创建两个常规Java线程:一个优先级为4的线程,另一个优先级为6的线程。
    • 该主线程通过调用Thread.sleep()睡眠五秒钟来故意阻止自身。
    • 在五秒钟的睡眠后,该线程指示其他两个线程终止。
  • 优先级为4的线程:
    • 该线程由原始线程创建,该原始线程执行包含for循环的run()方法。
    • 线程:
      1. 在每个循环迭代中增加计数。
      2. 通过调用Thread.yield()自愿放弃其时间片。
      3. 在请求主线程时终止。 在终止之前,线程会打印出循环计数。
  • 优先级为6的线程:此线程执行与优先级为4的线程相同的操作。

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

Created thread
Created thread
Thread terminates. Loop count is 540084
Thread terminates. Loop count is 540083

如果删除对Thread.yield()的调用,则两个线程的循环计数可能会接近,但极不可能完全相同。 在SCHED_OTHER策略中,两个线程都被分配了相同的默认优先级,因此,两个线程都被赋予了相同的时间片来执行。 因为线程执行相同的代码,所以它们应该具有相似的动态优先级调整,并从相同的运行队列以循环方式执行。 但是,由于优先级为4的线程首先执行,因此它在5秒执行间隔中应该占有更大的份额,并输出更高的循环计数。

了解RT线程

RT线程是javax.realtime.RealtimeThread 。 RTSJ要求规范的实现必须为RT线程提供至少28个优先级的连续范围。 这些优先级称为实时优先级 。 规范没有规定RT优先级范围的起始值,除非它的数值必须大于10-赋予常规Java线程的最高优先级值。 出于可移植性的原因,应用程序代码应使用新的PriorityScheduler类的getPriorityMin()getPriorityMax()方法来确定可用RT优先级值的范围。

RT线程的动机

JLS中的线程调度不精确,并且仅提供10个优先级值。 由Linux实施的POSIX SCHED_OTHER策略可以满足各种应用程序的需求。 但是SCHED_OTHER政策具有一些不利的特征。 动态优先级调整和时间分片可能会在不可预测的时间发生。 SCHED_OTHER优先级的数量(40)并不大,具有常规Java线程的应用程序已经假定了这些优先级的范围,并且可以进行动态优先级调整。 JVM还要求内部线程具有优先级,以用于特殊目的,例如垃圾回收(GC)。

缺乏确定性,需要更高的优先级以及与现有应用程序兼容的需求促使人们需要进行扩展,以便为Java程序员提供新的调度功能。 如RTSJ中所述, javax.realtime包中的类提供了这些功能。 在WebSphere Real Time中,Linux SCHED_FIFO调度策略解决了RTSJ调度需求。

使用RT Java线程进行线程调度

在WebSphere Real Time中,28个RT Java优先级的支持和有范围的11〜38的API PriorityScheduler类应该被用来检索此范围内。 本节描述了RTSJ中描述的线程调度的更多详细信息,以及Linux SCHED_FIFO策略中可以满足RTSJ要求的更多方面。

RTSJ认为RT优先级是由运行时系统在逻辑上实现的,并为每个RT优先级维护一个单独的队列。 线程调度程序必须从不为空的最高优先级队列的头部进行调度。 请注意,如果在任何队列中都没有用于RT优先级的线程,则会调度一个常规的Java线程,该线程按照JLS中的描述执行(请参阅使用常规Java线程进行的线程调度 )。

具有RT优先级的已调度线程被允许执行,直到阻塞为止,通过让步自动放弃控制,或者被具有更高RT优先级的线程抢占。 具有优先级的RT优先级的线程被置于队列的末尾以得到优先级。 RTSJ还要求这种调度必须在恒定时间内完成,并且不能根据诸如正在执行的RT线程数之类的因素而变化。 RTSJ的1.02版将这些规则应用于单处理器系统。 RTSJ并不要求在多处理器系统上如何进行调度。

Linux通过SCHED_FIFO策略提供了所有适当的RTSJ调度要求。 SCHED_FIFO策略适用于RT,而不适用于用户任务。 SCHED_FIFOSCHED_OTHER策略不同,它提供了99个优先级。 SCHED_FIFO不对切片线程计时。 此外, SCHED_FIFO策略不会通过“ 同步概述”部分介绍的优先级继承锁定策略来动态调整RT线程的优先级。 由于优先级继承,RTSJ需要调整优先级。

Linux为RT线程和常规Java线程提供了恒定的时间调度。 在多处理器系统上,Linux尝试模拟调度到可用处理器的RT线程的单个全局队列的行为。 这最接近RTSJ的精神,但是与用于常规Java线程的SCHED_OTHER策略确实有所不同。

使用RT线程的问题代码示例

清单2修改了清单1中的代码,以创建RT线程而不是常规Java线程。 使用java.realtime.RealtimeThread代替java.lang.Thread表示区别。 由getPriorityMin()方法确定,第一个线程在第4个RT优先级级别创建,第二个线程在第6个RT优先级级别创建。

清单2. RT线程
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
   volatile static boolean Stop = false;

   // Primordial thread executes main()
   public static void main(String args[]) throws InterruptedException {

      // Create and start 2 threads
      myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
      // want 1st thread at 4th real-time priority
      thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
      myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
      // want 2nd thread at 6th real-time priority
      thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      Thread.sleep(5*1000);
      Stop = true;
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         // Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

清单2中修改后的代码有一些问题。 如果程序在单处理器环境中运行,则它永远不会终止,并且仅输出以下内容:

Created thread

RT线程调度的行为可以解释此结果。 原始线程仍然是常规Java线程,并以非RT( SCHED_OTHER )策略运行。 原始线程启动第一个RT线程后,RT线程将抢占原始线程,并且RT线程将无限期运行,因为它不受时间量的限制,也不受线程块的限制。 抢占原始线程之后,将永远不允许执行该线程,因此它永远不会启动第二个RT线程。 Thread.yield()对允许执行原始线程没有影响-因为从逻辑上说,将RT线程放置在其运行队列的末尾-但线程调度程序会再次分派该同一线程,因为它是位于最前面的线程具有最高优先级的运行队列。

该程序在两处理器系统上也会失败。 它打印以下内容:

Created thread
Created thread

允许原始线程创建两个RT线程。 但是在创建第二个线程之后,原始线程被抢占,并且由于两个RT线程在两个处理器上执行并且永不阻塞,因此不允许原始线程终止线程。

该程序运行完成,并在具有三个或更多处理器的系统上产生结果。

在单个处理器上运行的RT代码示例

清单3显示了修改后的代码,使其可以在单处理器系统上正确运行。 main()方法的逻辑移至具有第8个RT优先级的“主” RT线程。 此优先级高于为主RT线程创建的其他两个RT线程中的任何一个的优先级。 具有最高的RT优先级可以使该主RT线程成功创建两个RT线程,并且还可以让主RT线程在从五秒钟的睡眠中唤醒时抢占当前正在运行的线程。

清单3.修改后的RT线程示例
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
   volatile static boolean Stop = false;

   static class myRealtimeStartup extends javax.realtime.RealtimeThread {

   public void run() {
      // Create and start 2 threads
      myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
      // want 1st thread at 4th real-time priority
      thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
      myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
      // want 1st thread at 6th real-time priority
      thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      try {
                        Thread.sleep(5*1000);
      } catch (InterruptedException e) {
      }
      myRealtimeThreadClass.Stop = true;
      }
   }

   // Primordial thread creates real-time startup thread
   public static void main(String args[]) {
      myRealtimeStartup startThr = new myRealtimeStartup();
      startThr.setPriority(PriorityScheduler.getMinPriority(null)+ 8);
      startThr.start();
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         // Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

当此程序在单处理器上运行时,将打印以下内容:

Created thread
Thread terminates. Loop count is 32767955
Created thread
Thread terminates. Loop count is 0

该程序的输出显示所有线程都在运行和终止,但是两个线程中只有一个执行for循环的迭代。 通过考虑RT线程的优先级来解释此输出。 RT主线程一直运行,直到被Thread.sleep()的调用阻塞为止。 主RT线程创建两个RT线程,但是在主RT线程Hibernate时,仅允许运行第6个RT优先级的第二个RT线程。 该线程一直运行到主RT线程从其睡眠中唤醒并告诉线程终止。 一旦主RT线程本身终止,则允许执行和终止第6个优先级的线程。 这样做并以非零值打印出循环计数。 该线程终止后,允许运行第4个RT优先级的线程,但由于已定向终止,因此它仅绕过了for循环。 该线程在终止之前会打印出零循环计数值。

RT应用程序的线程注意事项

本节讨论在移植应用程序以使用RT线程或编写新的应用程序以利用RT线程时需要考虑的RT线程功能。

RT线程的新扩展

RTSJ指定用于创建RT线程以在特定或相对时间启动的工具。 您可以创建一个线程,以在指定的时间间隔或时间段运行某些逻辑。 您可以定义一个线程有一个AsynchronousEventHandler (AEH)执行( 火 )时,这个逻辑在规定期限内未完成。 您还可以定义线程可以消耗的内存类型和数量的限制,以便在线程消耗的内存和内存限制超过此限制时引发OutOfMemoryError 。 这些功能仅适用于RT线程,不适用于常规Java线程。 您可以在RTSJ中找到有关这些功能的更多信息。

Thread.interrupt()和未决异常

Thread.interrupt()行为扩展到RT线程。 该API如JLS中所述中断被阻塞的线程。 通过将Throws AsynchronouslyInterruptedException子句添加到方法声明中,用户已将其显式标记为可中断的方法中,也会引发此异常。 从用户必须明确清除该异常的意义上说,该异常在线程上也很粘滞 。 否则它将保持绑定(称为挂起 )到线程。 如果用户不清除该异常,则线程可以在此绑定异常仍然绑定的情况下终止。 如果线程以“常规”方式终止,但在自己的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优先级运行,以便允许该程序抢占有问题的线程。

从Java优先级映射到操作系统优先级

在Linux上,POSIX SCHED_FIFO策略在1到99的整数范围内提供99个RT优先级。在此系统范围内,WebSphere VM使用优先级11至89,该范围的子集用于实现28个RTSJ优先级。 IBM WebSphere Real Time文档中描述了28个RT Java优先级到此POSIX系统优先级范围的映射。 但是应用程序代码不应依赖于此映射,而应仅依赖于Java级别上28个RT优先级的相对顺序。 这使JVM可以重新映射此范围,并在将来的WebSphere Real Time版本中提供改进。

如果应用程序需要的RT优先级高于或低于WebSphere Real Time中使用的RT优先级,则应用程序可以使用SCHED_FIFO优先级1或优先级90来实现守护程序或其他RT进程。

JNI AttachThread()

Java本机接口(JNI)允许您使用JNI AttachThread() API将用C代码创建的线程附加到JVM,但是RTSJ不会对JNI接口进行更改或配置以附加RT线程。 因此,应用程序应避免使用打算附加到JVM的C代码创建POSIX RT线程。 相反,应使用Java语言创建此类RT线程。

分叉的流程和RT优先级

线程可以派生另一个进程。 在Linux上,分叉进程的原始线程继承了对其进行分叉的父线程的优先级。 如果分叉的进程是JVM,则使用RT优先级创建JVM的原始线程。 这将违反常规Java线程(例如原始线程)对RT线程的调度优先级较低的顺序。 为了防止这种情况,JVM强制原始线程具有非RT优先级-即具有SCHED_OTHER策略。

Thread.yield()

Thread.yield()仅对具有相同优先级的线程产生执行,而从不对具有更高或更低优先级的线程产生执行。 仅Thread.yield()相同优先级的线程意味着Thread.yield()在使用多个RT优先级的RT应用程序中有疑问使用。 除非绝对必要,否则应避免使用Thread.yield()

NoHeapRealtimeThreads

javax.realtime.NoHeapRealtimeThread (NHRT)是RTSJ中的另一个新线程类型,它是javax.realtime.RealtimeThread的子类。 NHRT具有与我们所描述的RT线程相同的调度特性,除了NHRT不会被GC抢占并且NHRT不能读写Java堆。 NHRT是RTSJ的重要方面,将在本系列的后续文章中进行讨论。

异步事件处理程序

RTSJ新增了AsynchronousEventHandler (AEH),可以将其视为一种RT线程,在事件发生时执行。 例如,您可以将AEH设置为在特定或相对时间触发。 AEH还具有与RT线程相同的调度特性,并且具有堆和无堆两种形式。

同步概述

许多Java应用程序直接使用Java线程功能,或者正在开发的应用程序使用涉及多个线程的库。 多线程编程中的主要问题是确保程序在多个线程正在执行的系统中正确运行( 线程安全) 。 要使程序线程安全,可能需要使用锁或原子机操作等同步原语,对多个线程之间共享的数据进行序列化访问。 RT应用程序的程序员经常面临使程序在一定时间限制内执行的挑战。 为了迎接挑战,他们可能需要了解所使用组件的实现细节,含义和性能属性。

本文的其余部分讨论Java语言提供的核心同步原语的各个方面,这些原语在RTSJ中的变化以及RT程序员在使用这些原语时需要注意的一些含义。

Java语言同步概述

Java语言提供了三种核心同步原语:

  • 同步的方法和块允许线程在进入时锁定对象,并在退出时将对象解锁(锁定到方法或块)。
  • Object.wait()释放对象锁,线程等待。
  • Object.notify()解除对对象的线程wait() Object.notify()notifyAll()取消阻止所有服务员。

执行wait()notify()线程当前必须已锁定该对象。

当线程试图锁定已经被另一个线程锁定的对象时,就会发生锁争用 。 发生这种情况时,无法获取锁的线程将被放入对象的锁竞争者的逻辑队列中。 同样,多个线程可能在同一个对象上完成了Object.wait() ,因此该对象有一个逻辑上的服务员队列。 JLS没有指定如何管理这些队列,但是RTSJ确实规定了行为。

基于优先级的同步队列

RTSJ的精神是所有线程队列均基于FIFO和优先级。 基于优先级的FIFO行为-如先前的同步示例(其中选择最高优先级的线程随后执行)一样,也适用于锁竞争者和锁服务员的队列。 从逻辑角度来看,锁竞争者有一个基于FIFO优先级的队列,类似于等待执行的线程的执行队列。 锁服务员队列也很相似。

释放锁定后,系统会从竞争者优先级最高的队列的前面选择线程,以尝试锁定对象。 类似地,当notify()完成时,来自等待者优先级最高队列的前端的线程将不受其等待的阻塞。 锁释放或锁notify()操作类似于调度调度操作,在这种意义上,最高优先级队列的开头的线程已作用于该线程。

为了支持基于优先级的同步,需要对RT Linux进行修改。 WebSphere Real Time中的VM也需要进行更改,以将执行notify()操作时选择解除阻塞哪个线程的职责委托给Linux。

优先级倒置和优先级继承

优先级反转是指高优先级线程被低优先级线程持有的锁阻塞的情况。 中级优先级线程可以在持有锁并优先于低优先级线程运行时抢占低优先级线程。 优先级倒置会延迟低优先级线程和高优先级线程的进度。 优先级倒置造成的延迟可能会导致无法满足关键的截止日期。 图1的第一个时间轴显示了这种情况。

优先级继承是一种避免优先级倒置的技术。 RTSJ要求优先级继承。 优先级继承的思想是,在锁争用时,锁持有者的优先级被提高到希望获取锁的线程的优先级。 释放锁时,锁持有者的优先级被“解除激活”回到其基本优先级。 在刚刚描述的方案中,当发生争用锁时,低优先级线程会以高优先级运行,直到它释放锁的时间点为止。 在锁定释放时,高优先级线程将锁定对象并继续执行。 防止中优先级线程延迟高优先级线程。 图1中的第二条时间线显示了优先级继承生效时第一条时间线的锁定行为如何变化。

图1.优先级倒置和优先级继承
优先级倒置和优先级继承

在高优先级线程试图获取低优先级线程的锁的时候,低优先级线程本身可能被另一个线程持有的另一个锁阻塞。 在这种情况下,低优先级线程和其他线程被提升。 也就是说,优先级继承可能需要提升和取消一组线程的优先级。

优先继承实现

优先级继承是通过Linux内核功能提供的,该功能通过POSIX锁定服务导出到用户空间。 完全不希望在用户空间中找到解决方案是因为:

  • Linux内核可以被抢占,并可以进行优先级倒置。 某些系统锁也需要优先级继承。
  • 在用户空间中尝试解决方案会导致难以解决的竞争状况。
  • 提升优先级仍然需要内核调用。

POSIX锁定类型为pthread_mutex 。 用于创建pthread_mutex POSIX API使互斥锁实现优先级继承协议。 有POSIX服务可用于锁定pthread_mutex和解锁pthread_mutex 。 这些是优先级继承支持生效的地方。 在没有争用的情况下,Linux会在用户空间中执行所有锁定。 发生锁争用时,将在内核空间中完成优先级提升和同步队列管理。

WebSphere VM使用POSIX锁定API来实现我们前面描述的核心Java语言同步原语,并为优先级继承提供支持。 用户级C代码也可以使用这些POSIX服务。 在Java级别锁定操作时,将使用原子机器操作分配唯一的pthread_mutex并将其绑定到Java对象。 在Java级别的解锁操作时,只要没有争用该锁,就可以使用原子操作将pthread_mutex与该对象解除绑定。 通过争用,POSIX锁定和解锁操作将触发Linux内核优先级继承支持的发生。

为了帮助最大程度地减少互斥量分配和锁定时间,JVM会管理一个全局锁缓存和一个每个线程的锁缓存,其中每个缓存都包含未分配的pthread_mutex 。 A mutex in the thread-specific cache is acquired from the global lock cache. Before it is put into the thread lock cache, the mutex is prelocked by the thread. Uncontested unlock operations return a locked mutex to the thread lock cache. The assumption here is that uncontested locking is the norm, and POSIX-level locking is both reduced and amortized by reusing prelocked mutexes.

The JVM itself has internal locks used to serialize access to critical JVM resources such as the thread list and the global lock cache. These locks are based on priority inheritance and are held for short time intervals.

Synchronization considerations for RT applications

This section covers some features of RT synchronization that can help developers porting applications to use RT threads or writing new applications to exploit RT threading.

Lock contention between regular Java threads and RT threads

An RT thread can be blocked on a lock held by a regular Java thread. When this happens, priority inheritance takes over so that the regular Java thread is priority boosted to the RT thread's priority for as long as it holds the lock. The regular Java thread inherits all the scheduling characteristics of an RT thread:

  • The regular Java thread runs with SCHED_FIFO policy and so the thread does not time slice.
  • Dispatch and yielding happen from the RT run-queue of the boosted priority.

This behavior reverts to SCHED_OTHER when the regular Java thread releases the lock. If either of the threads created in Listing 1 were to run while holding a lock required by an RT thread, that program would not terminate and would exhibit the same problems we described in Problematic code example using RT threads . Because this situation is possible, doing spin loops and yielding is not advisable for any threads executing within a real-time JVM.

Lock contention between NHRTs and RT threads

An NHRT could block on a lock held by an RT thread (or a regular Java thread for that matter). While the RT thread holds the lock, GC could preempt the RT and indirectly preempt the NHRT. The NHRT needs to wait until the RT is no longer preempted by GC and then release the lock before the NHRT has a chance to execute. Preemption of an NHRT by GC can be a serious problem if the NHRT is performing a time-critical function.

The deterministic garbage collector in WebSphere Real Time keeps pause times under one millisecond, making the NHRT preemption more deterministic. If such pauses are not tolerable, you could bypass the problem by avoiding lock sharing between NHRTs and RT threads. If locking is mandatory, you could consider having RT- and NHRT-specific resources and locks. For example, applications that implement thread pooling could consider separate pools and pool locks for NHRTs and RT threads.

Also, the javax.realtime package provides the following:

  • The WaitFreeReadQueue class for the primary purpose of passing objects from an RT thread to NHRT.
  • The WaitFreeWriteQueue class for the primary purpose of passing objects from an NHRT to an RT thread.

These classes guarantee that an RT thread, which could be blocked by GC while performing a wait-free operation, won't hold a lock that an NHRT requires in performing a wait-free operation.

Synchronization in the javax.realtime package

Certain javax.realtime methods are intentionally not synchronized because overheads in synchronization occur even when a lock is uncontested. If synchronization is required, the caller is responsible for wrapping the required javax.realtime methods in synchronized methods or blocks. A programmer must consider adding such synchronization when the java.realtime package's methods are used.

Synchronization in the core JLS packages

In contrast, core JLS services such as java.util.Vector are already synchronized. Also, certain core JLS services can do some internal locking to serialize certain shared resources. Because of this synchronization, when using core JLS services, you must exercise care to avoid the problem of NHRT preemption by GC (see Lock contention between NHRTs and RT threads ).

Uncontested locking performance

Benchmarking and instrumentation of non-RT applications have shown that locking is predominantly uncontested. Uncontested locking is also believed to be the predominant case in RT applications, particularly when existing components or libraries are reused. It can be desirable to have a small and deterministic cost for locking that is known to be uncontested but where synchronization directives are hard to avoid or remove.

As we described earlier, an uncontested lock operation involves some setup and an atomic machine instruction. An unlock operation involves an atomic machine operation. The setup for a lock operation involves allocation of a prelocked mutex. The allocation is considered the largest variable cost in an uncontested lock operation. RealtimeSystem.setMaximumConcurrentLocks() can help control this variable cost.

RealtimeSystem.setMaximumConcurrentLocks(int numLocks) cause the VM in WebSphere Real Time to preallocate numLocks mutexes to the global lock cache. The global lock cache feeds the per-thread lock caches. By using this RealTimeSystem API, you can reduce the chance that lock initialization happens within a time-critical code region. RealTimeSystem.getMaximumConcurrentLocks() can be used to help decide what number should be used in the setMaximumConcurentLocks() call, but note that getMaximumConcurrentLocks() gives lock usage at the point of the call, not the high-water mark. A future RTSJ version may provide an API to provide the high-water mark. Do not provide ridiculously large values for the value of numLocks because the call to setMaximimConcurrentLocks() could take an inordinate amount of time and memory to create that many locks. Also note that this API is defined to be JVM specific, so other JVMs might ignore the call or provide different behavior.

Contested locking performance

A thread can simultaneously hold more than one lock, and these locks might have been acquired in a certain order. The set of all such locking patterns forms a lock hierarchy . Priority inheritance can mean the boosting and deboosting of a group of threads. The number of threads in the group should be no larger than the depth of the deepest lock hierarchy possible in the system. By keeping lock hierarchies shallow, which translates to locking as few objects as possible, you can affect the maximum number of threads that need to be priority adjusted.

Times in synchronization operations

Object.wait(long timeout, int nanos) provides nanosecond granularity for a relative wait operation. The HighResolutionTime.waitForObject() API is similar to Object.wait() and provides relative and absolute times that can be specified with nanosecond granularity. In WebSphere Real Time, both APIs are implemented with underlying POSIX locking wait services. These underlying services provide, at best, microsecond granularity. When required, and for portability, the getResolution() method of the javax.realtime package's Clock class should be used to retrieve the resolution for the executing platform.

摘要

The RTSJ extends and tightens threading and synchronization capabilities for the Java programmer through new RT classes and APIs in the javax.realtime package. In WebSphere Real Time, these capabilities are implemented by the RT version of the Linux kernel, modifications to POSIX threading, and modifications to the JVM itself. The deeper understanding you now have of RTSJ threading and synchronization can help you avoid problems as you write and deploy RT applications.


翻译自: https://www.ibm.com/developerworks/java/library/j-rtj3/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值