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 filesystems,
data loaders, data objects, cookie supports, hierarchy element
implementations, etc. Note that
AbstractFileSystem
assumes much of the burden of synchronization for typical file
system implementations, including the standard
LocalFileSystem
.
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
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
.
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:
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.
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.
To make calls into these APIs from non-event-queue threads, the
normal Swing mechanisms should be used, e.g.
SwingUtilities.invokeLater(...)
.
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
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.
The visualizer layer is expected to be removed in the future.
Module authors should be cautious about code run from
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.
Mutex
es.
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 -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
EventQueue
and SwingUtilities
.)
Typically an API subsystem has an associated public mutex or mutexes; other mutexes may be constructed if needed. For example, the project system has a single public mutex.
There is a special pseudo-mutex which has an interesting
implementation, namely to synchronize with the AWT event queue.
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).
The advantage of Mutex.EVENT over directly calling
EventQueue
methods (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)!
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 thread-safe status information, an exit
status, and the ability to fire termination events.
However, it is typically used in conjunction with a
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).
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.
DialogDisplayer
DialogDisplayer.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, nor wait from event queue for result of such operation).
ErrorManager.notify(...)
org.openide.util.datatransfer
is unsynchronized.
Action.actionPerformed(ActionEvent)
must be called from
the event thread. The action may choose to do all of its work
synchronously in this call, or if necessary do some work in another
thread, at its sole discretion.
org.openide.awt
should be used from the
event queue; org.openide.util.Enumerations
and
org.openide.util.io
are
unsynchronized. org.openide.util
is generally thread-safe.