使用多个线程仅调用一次方法

时间:2019-06-23 13:37:36

标签: java multithreading asynchronous thread-safety

我有一个Web应用程序,可以同时处理许多请求。 在应用程序的API方法之一中,我有一个方法-methodA()。在此方法中,我调用了另一个方法-doSomething()。 我有一种情况,我希望对methodA()的第一次调用将在单独的线程中运行doSomething()方法,但是此时,如果已经调用了对methodA()的另一个调用,请不要运行doSomething() )方法(因为它仍然由另一个线程运行),并继续执行methodA()的其余部分。

methodA() {
 .
 .
 doSomething() // In a new thread
 .
 .
}

我已经考虑过使用原子布尔值作为标志,但是我不确定这是否是最好的主意。

    private final AtomicBoolean isOn = new AtomicBoolean(false);
    methodA() {
        .
        .
        if (isOn.compareAndSet(false, true)) {
                     Runnable doSomethingRunnableTask = () -> { 
                         doSomething(); };
                     Thread t1 = new Thread(doSomethingRunnableTask);
                     t1.start();
                     isOn.set(false);
        } 

谢谢!

3 个答案:

答案 0 :(得分:0)

您可以使用ReentrantLock。锁一次只允许一个线程,并且其tryLock()方法将立即返回true或false,具体取决于是否获得了锁。

ReentrantLock lock = new ReentrantLock();

methodA() {
    ...
    if (lock.tryLock()) {
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    }
    ...
}

如果您想在另一个线程中执行doSomething(),并且不想阻塞任何调用线程,则可以使用与最初考虑的类似的东西。

AtomicBoolean flag = new AtomicBoolean();

methodA() {
    ...
    if (flag.compareAndSet(false, true)) {
        // execute in another thread / executor
        new Thread(() -> {
            try {
                doSomething();
            } finally {
                // unlock within the executing thread
                // calling thread can continue immediately
                flag.set(false);
            }
        }).start();
    }
    ...
}

答案 1 :(得分:0)

我认为您可以使用ReentrantLock,它是tryLock方法。来自ReentrantLock::tryLock

的文档
  

只有在调用时另一个线程未持有该锁时才获取该锁。

     

如果当前线程已经持有此锁,那么持有计数将增加1,并且该方法返回true。

     

如果锁由另一个线程持有,则此方法将立即返回false值。

因此,您可以在服务中作为字段创建此类锁定,以便调用methodA的线程将共享它,然后:

public class MyService {
    private ReentrantLock reentrantLock = new ReentrantLock();

    public void methodA() {
        if(reentrantLock.tryLock()) {
            doSomething();
            reentrantLock.unlock();
        }
    }
}

编辑: 在这里,锁将通过调用Thread来保持,该线程将等待提交的任务完成,然后解锁:

public class MyService {
    private ReentrantLock reentrantLock = new ReentrantLock();

    private ExecutorService pool = Executors.newCachedThreadPool();

    public void methodA() {
        if(reentrantLock.tryLock()) {
            Future<?> submit = pool.submit(() -> doSomething()); // you can submit your invalidateCacheRunnableTask runnable here.
            try {
                submit.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

还请记住,在此示例中我使用了threadPool,因此需要适当地关闭该池。

答案 2 :(得分:0)

我建议您使用blocking queue,而不要使用某种显式锁。我看到的好处是,如果需要,您将不需要重复生成线程。您只需要产生一个仅处理所有 doSomething 的线程即可。

场景:

当调用 methodA 时,它将专用线程的必要信息放入BlockingQueue并继续运行。专用线程会轮询来自BlockingQueue的信息(在空队列中进行阻塞)。当队列中收到某些信息时,它将运行您的 doSomething 方法。

BlockingQueue<Info> queue;

methodA() {
    //...
    queue.add(info);
    // non-blocking, keeps going    
}


void dedicatedThread(){
    for(;;) {
        //Blocks until some work is put in the queue
        Info info = queue.poll(); 
        doSomething(info);
    }

}

注意:我假设类型 Info 包含方法 doSomething 的必要信息。但是,如果您不需要共享任何信息,建议您使用信号量。在这种情况下,methodA会将票证放入信号灯中,而专用线程将尝试提取票证,直到收到一些票证为止一直阻塞。