2

I have a two part question...

  1. I have a class with a function in it that can only be accessed by any one thread at a given time. Making this a synchronized function or a synchronized block still allows for multiple threads since different threads are accessing it within the class. How can I make sure only one thread accesses this code? (See code example below)

  2. With the synchronized function, the calls to the function are queued up. Is there any way to only allow the last call to the function to access the code? So if I have Thread1 currently accessing my function, then Thread2 and Thread3 try to access it (in that order) only Thread3 will be given access once Thread1 is complete.

    public void doATask() {
        // I create a new thread so the interface is not blocked
        new Thread(new Runnable() {
    
            @Override
            public void run() {
                doBackgroundTask();
            }
        }).start();
    }
    
    private void doBackgroundTask(MyObject obj) {
        // perform long task here that is only being run by one thread
        // and also only accepts the last queued thread
    }
    

Thanks for any help!

Nick
  • 6,375
  • 5
  • 36
  • 53
  • what should the cancelled thread do? Should they be terminated or return a sentinel or throws an exception? Should the cancelled thread be cancelled as soon as another thread tried to call the method, or should it be cancelled when the Thread 1 is finished? There is too much ambiguity here, what are you trying to do? – Lie Ryan Mar 28 '13 at 02:41
  • What happens to thread 2? should it just exit when Thread 3 arrives? Throw some exception? – assylias Mar 28 '13 at 02:41
  • The second thread should just be terminated. It does not need to throw an exception. It can just be ignored. And the canceled thread can be canceled at any time...as soon as another thread tries to call the method OR when Thread1 is finished. – Nick Mar 28 '13 at 02:48
  • @Nick: if that's the case, then you probably don't want to use threading, instead you probably want some sort of event-driven system. – Lie Ryan Mar 28 '13 at 02:59
  • Well, the function doATask() is caused from a user pressing a button. In this case the user is scanning through a bunch of objects so theyre hitting the button quickly. – Nick Mar 28 '13 at 03:05
  • @Nick: in that case it will be easier. Most GUI systems are event-based, and you should be able to cancel the previous button event so only the last button event will be run. Or you could also disable the button while something is being processed. What GUI system are you using? – Lie Ryan Mar 28 '13 at 03:16

2 Answers2

2

If the second thread in your example can just return, you could use a combination of a lock and keeping track of the last thread executing the method. It could look like this:

private volatile Thread lastThread;
private final ReentrantLock lock = new ReentrantLock();

private void doBackgroundTask(Object obj) throws InterruptedException {
    Thread currentThread = Thread.currentThread();
    lastThread = currentThread;
    try {
        // wait until lock available
        lock.lockInterruptibly();
        // if a thread has arrived in the meantime, exit and release the lock
        if (lastThread != currentThread) return; 
        // otherwise
        // perform long task here that is only being run by one thread
        // and also only accepts the last queued thread
    } finally {
        lock.unlock();
    }
}

Full working test with additional logging that shows the thread interleaving and that T2 exits without doing nothing:

class Test {

    private volatile Thread lastThread;
    private final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        final Test instance  = new Test();
        Runnable r = new Runnable() {

            @Override
            public void run() {
                try {
                    instance.doBackgroundTask(null);
                } catch (InterruptedException ignore) {}
            }
        };
        Thread t1 = new Thread(r, "T1");
        Thread t2 = new Thread(r, "T2");
        Thread t3 = new Thread(r, "T3");
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        t3.start();
    }

    private void doBackgroundTask(Object obj) throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("[" + currentThread.getName() + "] entering");
        lastThread = currentThread;
        try {
            // wait until lock available
            lock.lockInterruptibly();
            // if a thread has arrived in the meantime, exit and release the lock
            if (lastThread != currentThread) return;
            // otherwise
            // perform long task here that is only being run by one thread
            // and also only accepts the last queued thread
            System.out.println("[" + currentThread.getName() + "] Thinking deeply");
            Thread.sleep(1000);
            System.out.println("[" + currentThread.getName() + "] I'm done");
        } finally {
            lock.unlock();
            System.out.println("[" + currentThread.getName() + "] exiting");
        }
    }
}

Output:

[T1] entering
[T1] Thinking deeply
[T2] entering
[T3] entering
[T1] I'm done
[T1] exiting
[T2] exiting
[T3] Thinking deeply
[T3] I'm done
[T3] exiting
assylias
  • 321,522
  • 82
  • 660
  • 783
2

What you want is probably a worker thread that waits for a signal to do some work. doATask() simply sends a signal to trigger the work. Accumulative signals are equivalent to one signal.

final Object lock = new Object();
MyObject param = null;

public void doATask(arg) 
    synchronized(lock)
        param=arg;
        lock.notify();

MyObject awaitTask()
    synchronized(lock)
        while(param==null)
            lock.wait();
        tmp=param;
        param=null;
        return tmp;

// worker thread

public void run()
    while(true)
        arg = awaitTask();
        doBackgroundTask(arg);
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • That is indeed another way to look at it - in which case you could simply use a blocking queue or some similar mechanism instead of wait/notify. – assylias Mar 28 '13 at 03:06
  • yeah, but the queue needs to keep only the last item. not sure which queue implements that. – ZhongYu Mar 28 '13 at 03:07