Search code examples
javamultithreadingclassloader

EDT changed to "system" group and Thread.currentThread().getContextClassLoader() is null


I suddenly got a weird problem in my application, but I am not sure if I can isolate the issue. I couldn't reproduce the bug in a SCCEE, but maybe someone could help me understand what happens by answering the 2 questions below.

The Context:

I have, basically this:

        ...
        Some treatment
       ->call to json-io to parse a Json String to Java Objects. see below
        ...

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                myUI.start();//starts my user interface
            }
        });

Usually, everything goes fine. But I added to the treatments a call to Json IO (a library that parses Json to Java and that I generally use without any trouble).

Now, one of my other library is yelling:

Caused by: java.lang.NullPointerException
    at net.sourceforge.jeuclid.elements.support.ClassLoaderSupport.loadClass(ClassLoaderSupport.java:65) 

After some researches, I discovered that it is because Thread.currentThread().getContextClassLoader() returns null.

I went to the run() above and discovered that the only difference between the 2 executions is that the Event Dispatched Thread that used to belong to the group main now belongs to system:

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread());
            //returns Thread[AWT-EventQueue-0,6,system] instead of Thread[AWT-EventQueue-0,6,main]
            myUI.start();//starts my user interface
        }
    });

In the end, I could solve the problem with

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
                myUI.start();//starts my user interface
            }
        }
    });

The Questions:

1) What kind of things can make the EDT change group ?

2) What are the consequences of writing Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); ? Is it a good or a bad idea ?


Solution

  • If your invocation of SwingUtilities.invokeLater is the first action that relies on the presence of the EDT, that thread will be created as a byproduct. So the created thread inherits the thread group of the thread which created it, e.g.

    ThreadGroup tg=new ThreadGroup("foo");
    new Thread(tg, ()->
        SwingUtilities.invokeLater(() -> System.out.println(Thread.currentThread()))
    ).start();
    

    when performed as first action of an application, will print

    Thread[AWT-EventQueue-0,6,foo]
    

    as you can verify on Ideone.

    But note that the thread group has no impact on the context class loader, it’s rather a symptom of the same cause. The context class loader is just inherited exactly like the thread group when the thread is created, e.g.

    ClassLoader dummyLoader=new URLClassLoader(new URL[0]);
    Thread.currentThread().setContextClassLoader(dummyLoader);
    SwingUtilities.invokeLater(() ->
        System.out.println(Thread.currentThread().getContextClassLoader()==dummyLoader));
    

    will print true; (verify on Ideone).

    So apparently, the context loader of the thread which invokes SwingUtilities.invokeLater, initiating the EDT creation, is already null (and that thread is in the system group). Setting the context loader to ClassLoader.getSystemClassLoader() means setting it to its default, so it has no negative impact unless you encounter an environment where the context loader is intentionally set to a non-default loader, though null can not be considered to be such a case. In other words, identifying the place, where it is set to null and fixing that, is the better choice.