Rather than attempt to make all calls in the APIs unconditionally thread-safe, which is not practical for the same reasons that much Java Platform code is not thread-safe in this way, the APIs are designed to have different threading requirements depending on which section of the APIs is being used. For the most part the division corresponds to API packages, although there are other considerations.
This document will begin by outlining several different
domains, each of which has its own threading
characteristics. Then, the APIs will be divided functionally into
the different domains. Programmers who pay attention to this
division and use the appropriate threading models throughout their
code are of course not guaranteed freedom from deadlocks, UI
freezes, race conditions, and multithreaded data structure
corruption, but the risk of these types of errors is greatly
reduced. Where appropriate, notes are included as to how
extensively a domain has been tested for safety.
Implementations of abstract API objects in these packages should
also follow this convention and use language-level synchronization
where needed. Such implementations include custom file systems,
data loaders, data objects, cookie supports, hierarchy element
implementations, etc. Note that
Accesses to individual nodes are also thread-safe,
e.g. metaproperties such as display name and icon, as well as
properties, sheets, and sheet sets. As for other APIs,
implementations of these objects must retain this thread safety, so
that for example any module-provided node property must somehow
synchronize its getter and setter methods, if the property class
does not do this automatically. Individual operations on the node
hierarchy (i.e. operations on
Swing
abstract text documents
are assumed to be thread-safe for callers, so implementors of
editors must take care to synchronize these objects to prevent
corruption. For atomicity of transactions on documents, callers
should use
Note that these packages do not generally protect the programmer
against delays, i.e. some calls may require a
user-perceptible amount of time to complete. In such cases, GUI
code should be careful when using the APIs, lest temporary screen
freezes or jumpiness occur. That said, all calls in these packages
which are expected to be slow provide a mechanism alleviating the
problem:
To make calls into these APIs from non-event-queue threads, the
normal Swing mechanisms should be used, e.g.
Note that nodes are not accessed in
the event queue, they are mutexed; however,
Explorer views are (and must be, since they are visual
components). To interface between these domains, the
Module authors should be cautious about code run from
Each mutex follows the usual model of permitting any number of
simultaneous readers, or a single writer, to exist in the mutex
block. (Also, re-entering a mutex with the same type of access is
harmless, as is entering in read mode while already holding write
mode. Trying to re-enter in write mode while holding a read mode
lock will cause a quick deadlock; to catch these, run the IDE with
the system property Typically an API subsystem has an associated public mutex or
mutexes; other mutexes may be constructed if needed. The principal
one is for the node hierarchy:
(Recall that non-hierarchy-related node operations, such as
manipulation of node properties, do not require this mutex and
ought not use it unless otherwise needed.)
There is a special mutex which has an interesting
implementation, namely to synchronize with the AWT event queue.
The advantage of Mutex.EVENT over directly calling
For example, the implementation of
All communication with such running processes is mediated
through the engine, which typically consists of events being fired
through the engine, and more importantly in the caller-side code
being returned some subclass of
Since these engine-driven processes are run quite independently
of other threads in the IDE, they must not block on such
threads. It is fine, however, for them to access primitive APIs
such as FileSystems and DataSystems, since such accesses can safely
coexist with accesses from elsewhere in the IDE.
The execution engine runs all of its executed tasks
independently of one another, i.e. it is possible to be
test-running multiple applications simultaneously. However, the
compilation engine (in its current implementation) imposes its own
threading structure on jobs:
Note that the Debugger API is technically completely threadsafe;
this is possible since there is no implementation to speak of in
it! In practice, a debugger would generally be an external process,
which means it would use the execution engine to manage its
progress; it is completely up to the debugger implementation to be
threadsafe and responsive, as there is no special engine for it to
use.
However, it is typically used in conjunction with a
It is also useful to post a request to the processor in the
middle of GUI code, if the request might take a perceptible amount
of time to complete.
Threading-Model Domains
Thread-safe
Several areas of the Open APIs are designed to be completely
thread-safe, which is to say that calls to these APIs may be made
from any thread without concern for deadlocks or corruptions. Most
importantly, the FileSystems API, DataSystems API, and Java
Hierarchy API fall into this category. All code in these APIs is
designed to expect concurrent access from multiple threads, and is
automatically synchronized against such a possibility.
AbstractFileSystem
assumes much of the burden of synchronization for typical file
system implementations, including the standard
LocalFileSystem
.
Children
)
are also thread-safe and atomic; however, some users may
wish to use their
mutex
for multi-stage operations.
NbDocument.runAtomic(...)
or
NbDocument.runAtomicAsUser(...)
as needed. Note that this thread-safety does not extend to
GUI components built using the documents, such as
EditorSupport.Editor
.
FileSystems has been extensively tested for thread safety and
appears to be quite robust. DataSystems has not undergone as
extensive testing but still has no known threading problems. The
implementation of threadsafe nodes is quite new as of this writing
(build 383), but it is expected that the reimplementation will
solve a number of previously intractable threading problems, as
well as simplifying the caller-side semantics. Threadsafe documents
are entirely up to the editor implementation, though there is
associated API code such as updates
DataFolder.getChildren()
is a synchronous call which ought not be used directly from GUI
code or other sensitive areas: when necessary, it finds new files
on disk and recognizes them with the data loader pool, which can be
slow. However, the corresponding folder node does not block when
yielding its list of children--this list may represent an
in-progress computation, as you can see when in the Explorer a new
folder with many files in it is expanded for the first time, and
objects appear as they are available.
SourceElement
,
while waiting to parse the source object. For this reason,
SourceElement.prepare()
and related calls is available to permit the parsing to be done
asynchronously;
upon completion of the parse, accesses to subelements should be
fast.
CloneableEditorSupport.prepareDocument()
may be used to load a text document
asynchronously,
as some editor implementations may not do this quickly (e.g. may
begin syntax analysis at load time).
SaveCookie.save()
if you wish the changes to be written back to disk; otherwise the
data object will be left in the modified state, which means an
editor component opened on it in the future will have an asterisk
in its name, and the user action to Save All will save it, etc.)
Line.Set
s
which should be safe.
Unsynchronized
Some utility APIs are unsynchronized and are only suitable for
access from within a single thread, as is common in utility classes
such as the Java Collections API. Such APIs do not interact
directly with other APIs and so this is reasonable. Such APIs
include enumerations, safe I/O, and data transfer.
AWT Event Queue
A number of pieces of the API are designed to operate as GUI
components; like in any AWT/Swing GUI application, care must be
taken to only access these APIs within the AWT event
queue, and not to consume an unreasonable amount of time or
block when running code within them. Most importantly, the entire
Explorer API is AWT-threaded, as well as the Window System API.
SwingUtilities.invokeLater(...)
.
Visualizer
class and some associated models such as
NodeTreeModel
are used by Explorer views to provide event-queue-safe calling
conventions while being asynchronously updated from the node
hierarchy. Other code which must run in the event queue could also
use these classes to interact safely with the node hierarchy.
Startup
Certain code is run (in the NetBeans private implementation of the
APIs) in the application main thread during IDE startup time. The
main impact of this on module authors is that some code may be run
in this main thread, specifically
module install code.
Generally such code should avoid waiting for the event queue
(though non-Window-System GUI objects such as dialogs may be
created at install or restore item, and Window System top
components at install time only).
ModuleInstall
implementation methods, using only thread-safe calls or tasks.
TopComponent
externalization methods may also be called in this main thread,
during Window System startup, as well as potentially in the event
queue, and thus should be as cautious.
Mutexes
Certain APIs are thread-safe, but for implementation purposes
require internal synchronization, and this synchronization is also
exposed to caller-side code which requires transactional
operations; this is generally done using
Mutex
es.
-Dnetbeans.debug.threads=true
.)
There are four calls which permit threads to access a mutex:
Mutex.readAccess(Runnable)
,
Mutex.readAccess(Mutex.Action)
,
Mutex.writeAccess(Runnable)
,
and
Mutex.writeAccess(Mutex.Action)
.
Collectively these provide not only read-versus-write access to the
mutex, but also the ability to either run code asynchronously
whenever the mutex is next available, versus running code with a
return value and blocking for it. (Compare similar calls in
SwingUtilities
.)
Children.MUTEX
is used by all API code which manipulates the parent-child
relationships among nodes. Simple caller-side code need not be
concerned with this, as basic operations such as getting a list of
children of a node, or adding one, will be automatically
thread-safe (and block if needed); more advanced users, such as
those wishing to add a node to a child list conditionally based on
the existence of another node in another list, may explicitly run
such code within the children mutex, being careful to choose write
access if there is any possibility of a change being made in that
thread.
Mutex.EVENT
makes no distinction between read and write access, permitting only
one thread at a time in each.
Mutex.EVENT.readAccess(Runnable)
(or Mutex.EVENT.writeAccess(Runnable)
)
has an effect equivalent to SwingUtilities.invokeLater
, while
Mutex.EVENT.readAccess(Mutex.Action)
(or Mutex.EVENT.writeAccess(Mutex.Action)
)
is equivalent to SwingUtilities.invokeAndWait
.
However, if the calling thread is already in the event
queue, this mutex executes the code immediately (rather than
waiting for other pending events to be processed).
SwingUtilities
(besides the special behavior for code
already in the event queue) is that it is possible to construct a
large amount of code to expect some mutex, and then quickly switch
between the event-queue mutex and some independent mutex. This was
used to advantage when creating an independent mutex for node
children (which previously had been an alias for the event-queue
mutex)!
Engines
There are a couple places in the APIs where certain types of
operations are clearly expected to take macroscopic time, if they
terminate at all, and thus have a special isolated threading
model. This applies to compiling and executing. In both cases, the
caller-side API is completely thread-safe (all API calls should
return quickly and not affect other threads). However, before being
run, implementation code is first safely wrapped in an
implementation-defined engine whose exact details are not
visible from an API perspective.
CompilationEngine
accepts a compiler job prepared by caller-side code, and then
begins running it (calling
CompilerJob.start()
and so on) in a totally separate thread group, insulated from the
calling thread and the event queue both.
ExecutionEngine
behaves similarly for execution. (Note: some compilers and
executors may additionally run external processes, and would
typically block on the Java process to exit.)
Task
which may be used to control the process in a simple way (see
below).
API users can at least be assured that compilation levels will be
run in the proper order.
Tasks
For explicit control over the performance of miscellaneous
long-running tasks, which require their own threads, the APIs
provides the utility class
Task
which occurs throughout the APIs as the result of various
asynchronous operations. In and of itself it does not do much
interesting except provide threadsafe status information, an exit
status, and the ability to fire termination events.
RequestProcessor
which does provide a separate thread or thread group to contain the
running tasks, as well as other functionality including task
priorities, and delayed scheduling (with the option to cancel a
pending job). Although callers can create their own request
processors, typical usage is to use a single instance in the
system with
RequestProcessor.postRequest(Runnable)
;
this ensures that all long-running code in the IDE (parsing,
internal database updates, and so forth) is serialized and
prioritized, rather than contending all at once for processor time
and triggering virtual memory thrashes (as might result from the
naive use of background threads).
Division of APIs into Domains
The Open APIs are divided into threading domains as follows:
TopManager.notify(...)
and similar calls (customize and select nodes) use the event queue in
GUI mode, however, and is assumed to block (so do not hold any locks
when you call it).
TopManager.notifyException(...)
is thread-safe.
org.openide.util.datatransfer
is unsynchronized.
ActionManager
which is itself normally used from the event thread. The action
manager should ensure that actions are run in a fashion appropriate
for the IDE mode. For example, in GUI mode no two actions should be
running in the same thread at the same time. In non-GUI mode, probably
all actions would be run synchronously. Actions which definitely
require the event thread should use it; most actions will either not
take much time all told (e.g.
SaveAction
);
create a GUI object and return to the caller (e.g.
InstantiateAction
);
create a modal dialog and block on it (or whatever
TopManager.notify
and similar do; e.g.
CustomizeAction
);
or invoke some process in an engine (e.g.
ExecuteAction
).
org.openide.awt
should be used from the
event queue; org.openide.util.enum
and
org.openide.util.io
are
unsynchronized. org.openide.util
is thread-safe.
Built on February 22 2001. | Portions Copyright 1997-2000 Sun Microsystems, Inc. All rights reserved.