Question

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!

Was it helpful?

Solution

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

OTHER TIPS

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top