Синхронизация с потоками

У меня есть вопрос из двух частей...

  1. У меня есть класс с функцией, которая может быть доступна только одному потоку в данный момент времени. Делая это synchronized функция или synchronized Блок по-прежнему допускает несколько потоков, так как разные потоки обращаются к нему внутри класса. Как я могу убедиться, что только один поток получает доступ к этому коду? (См. Пример кода ниже)

  2. С синхронизированной функцией вызовы функции помещаются в очередь. Есть ли способ разрешить только последний вызов функции для доступа к коду? Так что, если у меня есть Thread1, который в настоящее время обращается к моей функции, то Thread2 и Thread3 пытаются получить к ней доступ (в таком порядке), и только Thread3 получит доступ после завершения Thread1.

    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
    }
    

Спасибо за любую помощь!

2 ответа

Решение

Если второй поток в вашем примере может просто returnВы можете использовать комбинацию блокировки и отслеживания последнего потока, выполняющего метод. Это может выглядеть так:

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();
    }
}

Полный рабочий тест с дополнительной регистрацией, которая показывает чередование потоков и что T2 завершает работу, ничего не делая:

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");
        }
    }
}

Выход:

[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

Вероятно, вам нужен рабочий поток, который ожидает сигнала для выполнения некоторой работы. doATask() просто посылает сигнал для запуска работы. Накопительные сигналы эквивалентны одному сигналу.

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);
Другие вопросы по тегам