Threading and locking for model and view access

LuciadLightspeed uses various threads to prepare, process and display data. This way, LuciadLightspeed can take advantage of your computer’s multiple processor cores, and keep the UI responsive.

This article describes some basic threading and locking rules for LuciadLightspeed applications that you must comply with to prevent concurrency errors and ensure optimal efficiency. They apply to all operations that access LuciadLightspeed models and change LuciadLightspeed views.

Complying with these basic rules guarantees that you will not encounter any threading issues. Under specific circumstances you can make an exception to those rules, to deal with performance issues in your application for instance. For more information, see the article about advanced threading and locking. However, it is strongly recommended that you read the information here first, before you move on to more complex threading solutions.

Reading data in a LuciadLightspeed model

  • Always take read locks, and use TLcdLockUtil to do so.

  • You can take read locks on any thread, at any time.

Program: Taking a read lock
    try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(model);
         Stream<Object> elements = model.query(all())) {
      elements.forEach(consumer);
    }

Operations that require a read lock include:

  • Model operations like elements or applyOnInteract2DBounds

  • Element operations such as looping over points of a polyline

Changing data in a LuciadLightspeed model

  • Explicitly lock your models for all model writing, and use TLcdLockUtil to do so.

  • Use the application toolkit thread — the Event Dispatch Thread for Swing/AWT or the JavaFX Application Thread — for write locks.

  • Fire the TLcdModelChangedEvent for model operations outside the TLcdLockUtil lock.

Program: Taking a write lock
    try (TLcdLockUtil.Lock lock = TLcdLockUtil.writeLock(model)) {
      model.addElement(element, FIRE_LATER);
    } finally {
      model.fireCollectedModelChanges();
    }

Operations that require a write lock include:

  • Model operations, like addElement or elementChanged

  • Element operations, like adding points to a polyline

By applying these threading and locking rules for model changes, you prevent deadlocks in model listeners and ensure that they get events in the correct order. As a result, your model listeners can remain simple, because they do not have to take precautions for such problems.

Always call the fireCollectedModelChanges method as soon as you release a write lock. For instance, if you take two model write locks in a row, call fireCollectedModelChanges right after you release the first lock, and before you take the second lock. Next, call fireCollectedModelChanges again right after you release the second lock.

Changing view and layer properties

  • To perform view and layer operations, use the application toolkit thread.

View and layer operations include:

  • View changes, like map navigation, the addition and removal of layers, controller and georeference changes

  • Layer changes, like changes to visibility, properties or georeferences

Development pointers for threading and locking

Detecting threading rule violations automatically

Violations of the threading rules for model read and write locks can trigger subtle issues. See the reference documentation of ALcdModel to learn how certain types of violations can be detected automatically.

Thread-safety of LuciadLightspeed objects

LuciadLightspeed objects are not thread-safe, unless their thread-safety is stated explicitly in the reference documentation.

Locking custom models that depend on other models

Suppose that you implement your own model A, which wraps a model B. Model A relies on its model B for read and write operations, but both model A and B are also used externally, because you added them to a view for example. If you read or write from model A, you must lock both model A and model B, because other threads may also access model B.

To make this transparent to users, model A can implement ILcdLockDependent and specify model B as its lock-dependent object. Whenever someone locks model A with TLcdLockUtil, its dependent objects is also locked automatically.

Switching to the toolkit thread

If you need to switch to the toolkit thread from another thread to execute an operation, use one of the invoke methods in EventQueue for Swing/AWT, or the runLater method in Platform for JavaFX.

Using a non-toolkit thread for model changes

You can use a thread other than the application toolkit thread only if your model is not part of a view yet, or if you need to offload the toolkit thread for performance reasons. The advanced threading and locking article explains this in detail.

Consider using a non-toolkit thread if:

  • The performance of your application is hampered by a frequently blocked toolkit thread. Therefore, you wish to offload the toolkit thread and move some of the work load to another thread.

  • You are using offscreen views.