How to port the libjava threading layer

Introduction

The libjava threading layer was designed to expose precisely those pieces of a thread system that are required by a Java runtime. It also turns out to be fairly easy to port.

The First Step

The first step to porting the threading layer is adding code to libjava/configure.in to handle the new system. This is fairly straightforward, so I won't go into any details here.

It might also be necessary to modify other configure scripts as well. For instance, you might need to modify qthreads/configure so that qthreads won't be built when the new thread system is in use. (This is actually already done, but might need revisiting if we change libjava/configure to automatically determine thread package based on host. In this case, qthreads and boehm-gc must make the same determination.)

Porting Gcc

Pieces of the gcc runtime code, in particular the exception handling, need to know about the thread system that is in use, so any new thread port for libjava must also involve adding new code to gcc. Generally this is much easier than writing the thread code for libjava.

  • Add support to gcc/configure.in to support the new thread system. gcc's configure script understands --enable-threads just as libjava's does; the two must agree so that a single configure from the top level will succeed.
  • Write a new ``gthr-PORT.h'' header file. See the existing gthr.h to see what must go into this file.
  • Contact the gcc maintainers and try to get your patch installed. Allocate plenty of time for this step.
  • Update java/jvspec.c to tell it about the new thread library.

The Header File

The second step is to write a header file which includes all the declarations that are required by libjava. This step is the bulk of the work involved.

This header file consists of a number of definitions and declarations. At configure time, a link named ``java-threads.h'' will be created to point to the appropriate package-specific header.

The contents of this file are fixed in the sense that the list of declarations which must appear is fixed. The reason there is a separate such header per thread system is so that each system can make its own decisions about which functions should be inlined.

A useful piece of information which doesn't seem to belong anywhere else: in Java, a thread can be marked as a ``daemon'' thread before it is started. The runtime exits when all non-daemon threads have exited. The thread porting layer is responsible for handling this somehow.

Boilerplate

Each thread header must contain a declaration of the type ``_Jv_ThreadStartFunc''. This typedef is the same in every thread package. Declare it thusly:

typedef void _Jv_ThreadStartFunc (java::lang::Thread *);

Mutexes

Mutexes (or "mutices" as some would have it) are one of two fundamental synchronization objects used by libjava. A mutex is a lock that is held by a single thread. A thread can hold the same mutex multiple times; they are reference counted. When one thread holds a mutex, another thread attempting to grab the mutex will block until the mutex is free.

Implementing mutexes requires a typedef and several functions:

_Jv_Mutex_t
A package-specific typedef which represents a mutex. This type must be fully declared in the thread header, as memory allocation for mutexes is handled by the runtime and not by the thread layer.
void _Jv_MutexInit (_Jv_Mutex_t *mutex)
A function to initialize a mutex.
void _Jv_MutexDestroy (_Jv_Mutex_t *mutex)
A function to destroy a mutex.. This function is optional. If you define it, you must also define the preprocessor macro _Jv_HaveMutexDestroy. The presence of this function incurs a performance penalty.
int _Jv_MutexLock (_Jv_Mutex_t *mutex)
Lock the mutex. Note that Java mutexes are recursive, so this function must maintain a lock-depth counter in the _Jv_Mutex_t if the underlying platform's mutex is not recursive. Returns 0 on success, -1 on failure.
int _Jv_MutexUnlock (_Jv_Mutex_t *mutex)
Unlock the mutex. Returns 0 on success, -1 on failure.

Condition Variables

Condition variables are the other synchronization primitive used by libjava. They are used to implement the wait and notify functions.

Each condition variable has an associated mutex. A thread acquires the mutex and then waits on the condition variable. This waiting takes the form of an atomic release of the mutex (fully releasing it, if it is held several times by the thread) followed by blocking. When the condition variable is signalled by some other thread, the waiting thread reawakens, re-acquires the mutex (restoring it to its previous state -- the lock depth counting must be handled correctly), and returns. It's possible for several threads to wait on a condition variable at the same time, and to all be reawakened simultaneously (of course, only one thread at a time can re-acquire the mutex).

Once again, a typedef and several functions are required.

_Jv_ConditionVariable_t
A package-specific typedef which represents a condition variable.
void _Jv_CondInit (_Jv_ConditionVariable_t *condvar)
Initialize a condition variable.
void _Jv_CondDestroy (_Jv_ConditionVariable_t *condvar)
Destroy a condition variable. This function is optional. If you define it, you must also define the preprocessor macro _Jv_HaveCondDestroy. The presence of this function incurs a performance penalty.
int _Jv_CondWait (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex, jlong milliseconds, jint nanoseconds)
Wait on the condition variable using the associated mutex. The final arguments are a timeout; if 0 then no timeout is to be used. It isn't necessary to implement nanosecond resolution. However, the best possible resolution should be implemented. _Jv_CondWait should check that MUTEX is locked by the current thread. If it isn't, it should return _JV_NOT_OWNER. The interrupted status flag of the calling thread should be checked before _Jv_CondWait blocks, if this is not done automatically by the OS thread layer. If the thread is interrupted (either before or during the wait), _JV_INTERRUPTED should be returned. The interrupted status flag is not cleared. _Jv_CondWait should never throw an exception directly. Returns 0 if CONDVAR was signalled normally, or if the timeout expired.
int _Jv_CondNotify (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex)
Signal the condition variable. This wakes up one thread waiting on the variable. Returns 0 on success. Should return _JV_NOT_OWNER if current thread does not hold the mutex.
int _Jv_CondNotifyAll (_Jv_ConditionVariable_t *condvar, _Jv_Mutex_t *mutex)
Wake up all threads waiting on the condition variable. Returns 0 on success. Should return _JV_NOT_OWNER if current thread does not hold the mutex.

Everything Else

There is one remaining typedef that must be defined: ``_Jv_Thread_t''. This is a typedef specific to the threads system which represents the thread system's notion of a thread. Each ``java.lang.Thread'' object has an associated native thread object of this type.

The remaining functions have to do with thread creation and manipulation.

Thread Creation and Destruction

void _Jv_InitThreads (void)
Initialize the thread system. This is called before any other initialization is done. This should not be used to start the first thread; that is handled elsewhere. This probably won't be needed by most thread packages.
_Jv_Thread_t * _Jv_ThreadInitData (java::lang::Thread *thread)
This is called when a new Java thread object is created. The thread system should create a new thread, allocate any data needed to assist the implementation of mutexes and condition variables, initialize it as needed, and return a pointer to it (this pointer is opaque to generic threads code). The thread should not be started yet -- in Java, thread creation and execution are separate.
void _Jv_ThreadDestroyData (_Jv_Thread_t *data)
This function should clean up and/or free any native resources allocated for the thread in _Jv_ThreadInitData. It is called when the Java thread object is finalized.
void _Jv_ThreadStart (java::lang::Thread *thread, _Jv_Thread_t *data, _Jv_ThreadStartFunc *method)
Start running the indicated thread. This function should examine the Thread object and make use of the priority, daemon-ness, etc, if possible. The argument ``data'' holds the data element previously initialized with _Jv_ThreadInitData. The ``thread'' argument should be passed as the sole argument to ``method''.
void _Jv_ThreadWait (void)
Wait for all non-daemon threads to exit.
void _Jv_ThreadRegister (_Jv_Thread_t *data)
This is called from a newly created thread before the run() method is invoked, or when a native thread is attached to the Java runtime via JvAttachThread. It can be used to create and/or register thread-specific data to assist the implementation of _Jv_ThreadCurrent, for example.
void _Jv_ThreadUnRegister ()
This is called from a thread which is about to die, because its run() method has returned or it is being "detached" from the Java runtime. It gives the thread layer a chance to clean up thread-specific data allocated in _Jv_ThreadRegister. Note that the thread object may still be visible to Java, so _Jv_Thread_t data should only be freed by _Jv_ThreadDestroyData.

Truly Miscellaneous Functions

java::lang::Thread *_Jv_ThreadCurrent (void)
Each thread package is required to maintain a mapping from the current thread to the associated Java Thread object representing that thread. Typically this is done by storing the pointer (from the _Jv_ThreadInitData or _Jv_ThreadStart calls) to the thread object in thread-local storage. This function returns that object.
void _Jv_ThreadYield (void)
Cause the current thread to yield. Strictly speaking this need not do anything.
void _Jv_ThreadSetPriority (_Jv_Thread_t *data, jint priority)
This is called when the priority of a thread is set. Priorities are remembered and manipulated in the Thread class; this call exists in case the underlying thread system has a useful notion of priorities to which Java priorities should be mapped.
void _Jv_ThreadInterrupt (_Jv_Thread_t *data)
This function is called by Thread.interrupt(). It should set the interrupt_flag of the Thread object corresponding to DATA, and cause any "slow" or "blocking" system I/O or a call to _Jv_CondWait to return immediately. Interrupting of _Jv_CondWait calls should be implemented in such a way that simultaneous interrupt() and notify() calls always result in one thread waking up normally (if there are enough threads waiting). That is, if two threads are waiting up on condition variable and one of them is interrupted while the variable is concurrently notified by another thread, then EITHER _JV_INTERRUPTED should be returned for one thread's _Jv_CondWait() and the others to return normally, or the interrupted thread's _Jv_CondWait() call should just return normally (but its interrupted status flag should be set). It is also important to prevent possible races in the interrupt implementation: it should not be possible for an interrupt to be delivered to a thread immediately after it checks its interrupted status flag but before it enters an interruptible blocking call, for example.

Unimplemented functionality

Some Java functionality remains unimplemented in libgcj. Some of this will change, but some will not.
Thread.destroy
Thread.resume
Thread.suspend
Thread.stop
These functions are unimplemented and never will be implemented. They are deprecated in JDK 1.2. The current implementations simply throw an exception.

Grey Areas

There are still a few areas which have not been fully explored. This will change over time.

  • There is no documented way for the thread system and the GC to interact. This is a historical accident, as our only GC right now knows about thread systems itself.
GCJ

GCJ Home
GCC Home
Status
FAQ
Documentation
Contributing
Done with GCJ

About GCC
Mission Statement
Releases
Snapshots
Mailing lists
Contributors
Steering Committee
@gnutools on Twitter@gnutools
Documentation
Installation
· Platforms
· Testing
Manual
FAQ
Wiki
Further Readings
Download
Mirror sites
Binaries
"Live" Sources
SVN read access
Git read access
Rsync read access
SVN write access
Development
Development Plan
· Tentative Timeline
Contributing
Why contribute?
Open projects
Front ends
Back ends
Extensions
Benchmarks
Build Robot
Translations
Bugs
Known bugs
How to report
Bug tracker
· Management