Синхронизация с потоками
У меня есть вопрос из двух частей...
У меня есть класс с функцией, которая может быть доступна только одному потоку в данный момент времени. Делая это
synchronized
функция илиsynchronized
Блок по-прежнему допускает несколько потоков, так как разные потоки обращаются к нему внутри класса. Как я могу убедиться, что только один поток получает доступ к этому коду? (См. Пример кода ниже)С синхронизированной функцией вызовы функции помещаются в очередь. Есть ли способ разрешить только последний вызов функции для доступа к коду? Так что, если у меня есть 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);