On Tue, 4 Mar 2025 21:24:46 GMT, Kevin Rushforth <k...@openjdk.org> wrote:
>> I think the problem is in the callbacks themselves, as they'd be on the >> wrong thread. I vaguely remember experimenting with a system where you can >> provide an Executor for callbacks (like `Platform::runLater`) to mitigate >> this. > > 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()); } } } ------------- PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1980380695