On Tue, 4 Mar 2025 22:59:15 GMT, John Hendrikx <jhendr...@openjdk.org> wrote:
>> The listener callbacks will always be on the thread that mutates the >> property being listened to, so that isn't the problem. The problem in this >> case is that adding or removing a listener while another thread is modifying >> the property, which will iterate the list of listeners, is not thread-safe. >> Consider the following: >> >> >> final var prop = new SimpleBooleanProperty(false); >> >> // add listeners in a background thread >> var thr = new Thread(() -> { >> for (int i = 0; i < 10000; i++) { >> ChangeListener<Boolean> listener = (obs, o, n) -> { >> if (!mainThread.equals(Thread.currentThread())) { >> System.err.println("***** ERROR: listener called on >> wrong thread: " + Thread.currentThread()); >> } >> }; >> prop.addListener(listener); >> } >> }); >> thr.start(); >> >> // Fire events in main thread >> for (int jj = 0; jj < 10000; jj++) { >> prop.set(!prop.get()); >> } >> >> >> The listeners all get called on the (correct) main thread, but continually >> adding listeners while the property is modified will lead to AIOOBE or NPE >> (the above example gets random NPEs when I run it). > > Yes, but that's because there is no synchronization. Here is a version that > does do synchronization; I see no more exceptions (and it runs just as fast > really): > > > public class PropTest { > public static void main(String[] args) { > Thread mainThread = Thread.currentThread(); > final var prop = new SimpleBooleanProperty(false) { > @Override > public synchronized void addListener(ChangeListener<? super Boolean> > listener) { > super.addListener(listener); > } > > @Override > public synchronized void set(boolean newValue) { > super.set(newValue); > } > }; > > // add listeners in a background thread > var thr = new Thread(() -> { > for (int i = 0; i < 1000000; i++) { > ChangeListener<Boolean> listener = (obs, o, n) -> { > if (!mainThread.equals(Thread.currentThread())) { > System.err.println("***** ERROR: listener called on wrong > thread: " + Thread.currentThread()); > } > }; > prop.addListener(listener); > } > }); > thr.start(); > > // Fire events in main thread > for (int jj = 0; jj < 1000000; jj++) { > prop.set(!prop.get()); > } > } > } Of course for a generic solution, we could provide a wrapper for properties, or custom synchronized properties. ------------- PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1980382245