Saturday, June 19, 2010

Invoke code on a certain thread in Java

I think Java should be a bit jealous on the .NET's interesting feature of being able to invoke a piece of code on any given thread. In .NET that's possible by creating a Control (say myControl) on a certain thread and then using myControl.Invoke or myControl.BeginInvoke having as parameter the piece of code you want to invoke on that thread. The parameter is a delegate, another feature missing in Java, but let's ignore this for now.
In Java we have SwingUtilities.invokeAndWait and SwingUtilities.invokeLater to invoke code on the event dispatching thread, but not on any given thread. They take a Runnable as parameter.

So how would we implement such a feature in Java ? We'll need a thread that has nothing better to do but try to dequeue items from its queue and invoke them (the items would be the pieces of code that we need invoked on that thread). Something like this:

package threading;


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


public class Invoker {
    
    private static Thread myThread;
    private static BlockingQueue<Task> queue;
    
    private Invoker() {
    }
    
    public static void BeginInvoke(Task task) {
        if (myThread == null) {
            queue = new LinkedBlockingQueue<Task>();
            myThread = new MyThread(queue);
            myThread.start();
        }
        if (Thread.currentThread() != myThread) {
            try {
                queue.put(task);
            } catch(InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
        } else {
            task.perform();
        }
    }


}


And the actual thread (I simulate the .NET delegate by using Java anonymous classes):


package threading;


import java.util.concurrent.BlockingQueue;


public class MyThread extends Thread {
    private final BlockingQueue<Task> taskQueue;
    
    public MyThread(BlockingQueue<Task> queue) {
        taskQueue = queue;
    }
    
    public void run() {
        while(true) {
            try {
                Task currentTask = taskQueue.take();
                currentTask.perform();
            } catch (InterruptedException e) {
                e.printStackTrace();
                // Restore the interrupted status
                Thread.currentThread().interrupt();
            }
        }
    }


}


We need an interface for our task - either an existing one (Swing uses Runnable), or a new one, like:

package threading;


public interface Task {


    void perform();
}


Finally, the test class with the main method:


package threading;


public class Test {


    public static void main(String[] args) {
        System.out.println("main thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
        Invoker.BeginInvoke(new Task() {
            public void perform() {
                System.out.println("perform task on thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
                
            }
        });
        Invoker.BeginInvoke(new Task() {
            public void perform() {
                System.out.println("perform task again on thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
                
            }
        });
    }
}

It should come as no surprise the output:

main thread: 1 main
perform task on thread: 7 Thread-0
perform task again on thread: 7 Thread-0

No comments:

Post a Comment