Thread Priority on the Solaris™ Platform

This document discusses how the Java™ virtual machine (JVM) maps priorities for threads executing in the JVM (Java threads) onto native thread priorities on Solaris. It covers both current and past implementations of Solaris threads and the JVM.

Background Information: Java Threads

The JVM defines a range of ten logical priorities for Java threads, including:

java.lang.Thread.MIN_PRIORITY  = 1
java.lang.Thread.NORM_PRIORITY = 5
java.lang.Thread.MAX_PRIORITY  = 10

These values [1..10] are passed into Thread.setPriority(int) to assign priorities to Java threads. The default priority of a Java thread is NORM_PRIORITY. (A Java thread that doesn't explicitly call setPriority runs at NORM_PRIORITY.) A JVM is free to implement priorities in any way it chooses, including ignoring the value.

The Java HotSpot™ virtual machine currently associates each Java thread with a unique native thread. The relationship between the Java thread and the native thread is stable and persists for the lifetime of the Java thread.

Background Information: Solaris

Libthread Variants: T1 and T2

Prior to Solaris 9 the default libthread was the so-called T1 libthread. T1 provided an M:N threading model where M native threads were multiplexed on top of N kernel threads (LWPs). The relationship between native threads and LWPs was fluid and dynamic and could change even while a thread was running and without the knowledge of the thread. Solaris provided the priocntl() system call to change the dispatching priority of an LWP, but because the relationship between LWPs and native threads was unstable so there was no reliable way to change to change the dispatching priority of a native thread. (The JVM could change the priority of the LWP on which a Java thread was currently running, but the thread could switch to another LWP without the JVM's knowledge).

T2, the default libthread in Solaris 9 and better, implements a much more simple and robust 1:1 threading model. Each native thread is assigned to a unique LWP, and that relationship is stable for the lifetime of the native thread.

Both T1 and T2 expose a thr_setprio() API that applications use to set a thread's process-local priority. The value assigned by thr_setprio() is a process-local attribute and is not visible to the kernel scheduler. The thr_setprio()-priority controls the placement and ordering of threads on user-level process-local sleep queues, such as the queue of threads associated with a contended process-local mutex. In HotSpot most mutexes are uncontended and condvars have usually have either 0 or 1 threads. As such, the ordering imposed by thr_setprio()-priority has very little effect on most Java threads. The thr_setprio() function supports priority values in the range 0 through 127, inclusive, with 127 representing the highest priority.

T1 also uses thread priority to implement rudimentary user-mode preemption. T1 maintains an invariant that the thr_setprio()-priority of any thread on the local ready queue must be less than or equal to the priority of any unbound thread that's currently running and associated with an LWP. If the invariant is threatened, T1 preempts the lowest priority executing thread to "steal" its LWP in order to reestablish the invariant.

Preemption can occur in these cases:

An early version of T2, referred to as the alternate libthread, appeared in Solaris 8.

For additional information about T1, T2, and LWPs refer to the following:

Native LWP Priorities

In Solaris an LWP's priority influences how many CPU cycles a thread receives relative to other threads. The Solaris scheduler uses priority (among other factors) to determine if one thread should preempt another thread, how often a thread runs, and how long a thread runs. Native LWP priorities are assigned by the priocntl() system call.

Summary

To recap, we have Java threads, with priorities set by the Thread.setPriority method. Java threads run on native threads. The thr_setprio() function is used to change the priority of native threads. Native threads run on LWPs. The priocntl() system call is used to change the priority of LWPs.

Historical Thread Priority Implementations

Before 1.4.2

In releases earlier than 1.4.2, when a Java thread called the Thread.setPriority method or when a thread was created, HotSpot would call thr_setprio() to map the Java priority to an native priority. Calling thr_setprio() had very little effect on the execution behavior of of a Java thread. The JVM did not call priocntl() to adjust the priority of the underlying LWP. This was a conscious design decision because in the 1.4.2 time frame the only libthread available on Solaris was the older T1 libthread.


Note: The JVM could have forced native threads to be bound 1:1 to LWPs under T1 by specifying THR_BOUND when threads are created. THR_BOUND is not sufficient, however, as threads that attach to the JVM might not be THR_BOUND and the primordial thread is not THR_BOUND. Given that there's no way in Solaris to force a thread to be bound after it's been created, the HotSpot implementors decided that it was prudent not to change LWP priority when a Java thread called setPriority()

1.4.2

In 1.4.2 HotSpot was able to determine at startup time if it was running under T1 or T2. If the JVM started under T1 the effect of priorities would be precisely the same as in earlier releases.

Under T2, however, 1.4.2 translated calls to the Thread.setPriority method into calls to both thr_setprio() (to change the native process-local priority) and to priocntl() (to change the dispatching priority of the underlying LWP). The JVM called priocntl() only for threads that are running in the TS (timeshare), IA (interactive), and RT (real-time) scheduling classes. Refer to the Solaris priocntl(2) man page for a description of scheduling classes. If a Java thread was not in the TS, IA, or RT scheduling classes the JVM would not attempt to set the priority of the underlying LWP with priocntl().

Unfortunately the default priority of native threads in the TS and IA scheduling classes is the highest possible priority. The default logical priority for Java threads is NORM_PRIORITY, which is midway in domain of Java thread priorities. When the JVM maps NORM_PRIORITY to native and LWP priorities, the outcome is a value that is less then the default native priority. Let's say we have a 2-CPU system running a JVM, and that JVM has two Java threads, both at NORM_PRIORITY. Assume that the threads are in the IA or TS scheduling class, as is commonly the case. When the Java threads are created, the JVM calls priocntl() to map NORM_PRIORITY to the middle of the TS or IA priority bands. Furthermore, assume that 2 native "C" threads in another process are running/ready concurrently with the Java threads. Both the native C threads and the Java threads are CPU-bound and spin, computing. The native threads will run at the highest priority in the TS or IA scheduling class, and the JVM threads will run at the middle priority. Since all four threads are competing for CPU cycles, the native threads will receive relatively more CPU cycles and the Java threads will, in a sense, be disadvantaged. This effect occurs only when Java threads compete with normal threads and the system is entirely saturated.

Conversely, a benefit of using lower relative priorities is that in the TS and IA scheduling classes a thread running at lower priorities receives a longer quantum, assuming that it's not preempted mid-quantum by higher priority threads that become ready. A longer quantum is often beneficial for threads in server applications as the context switch rate from preemption decreases. A thread is permitted to run on a CPU for a longer period and the cache-reload transient (the period immediately after a thread is scheduled on to a CPU — when a thread incurs a high cache miss rate as it repopulates the CPU's data caches and displaces the previous thread's data) is amortized over a longer quantum.

JRE 5.0

JRE 5.0 provides the same priority mapping as 1.4.2 except that Java priorities in the range [10...5] are all mapped to the highest possible TS or IA priority, while priorities in the range [1..4] are mapped to correspondingly lower native TS or IA priorities. The advantage of this change is that Java threads at NORM_PRIORITY can now compete as expected with native threads. If neither the Java threads nor the native threads have explicitly set priorities (which is commonly the case), both classes of thread will compete on an equal footing, running at the highest priority in the TS or IA scheduling class.

Assuming that Java threads don't explicitly set their priority with setPriority(), this change restores the behavior and effective LWP priority of Java threads that was used prior to 1.4.2. The disadvantage to this implementation is that Java priorities from 5 to 10 are not differentiated. A Java thread at logical priority 8 maps to the same LWP priority as a Java thread at priority 9, for instance.

Miscellany

The following statements apply to all versions of HotSpot:

General Scheduling Issues: Priority, Yielding, and Monitor Fairness

The Thread.setPriority and Thread.yield methods are advisory. They constitute hints from the application to the JVM. Properly written, robust, platform-independent code can use setPriority() and yield() to optimize the performance of the application, but should not depend on these attributes for correctness. Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide.