Java是否有一个内置的锁定机制,其中一个“取一个数字”(建立将来的锁定顺序),进行一些工作,然后阻止,直到“现役”读取我们的机票号码?
这是我的意思的伪代码概述:
public CompletionStage<Void> onEvent()
{
return CompletableFuture.runAsync(() ->
{
// Establish future locking order without acquiring a lock
int ticket = takeTicket();
// Execute network request asynchronously
return CompletableFuture.runAsync(() -> networkRequest(), threadPool).
thenCompose(() ->
{
// Trying acquiring a lock. If the locking system is
// waiting for someone with a smaller number, block
// until our turn comes up.
lock(ticket);
// ... do work that depends on strict event ordering ...
return CompletableFuture.runAsync(() ->
{
fireMoreEvents();
// Permanently discard the lock, allowing the next user to lock.
release(ticket);
}, eventDispatcher);
});
}, eventDispatcher);
}
timeout
毫秒内没有锁定和解锁,请抛出异常(以表明我这方面存在某种编程错误)。如果这不是Java内置的,是否有一种很好的方法来实现此目的,而不必每次增加服务数量时都唤醒所有阻塞的线程?
我有一个事件侦听器,该侦听器必须按接收事件的顺序记录事件,但它必须在数据旁边记录来自异步源(例如网络)的相关信息。
答案 0 :(得分:0)
据我了解,我们有许多并发(主要)任务,每个任务都有一个关联的清除任务,这些任务要在执行主要任务后运行。
我们要对清理任务强加命令;而且我们也不希望任何两个线程同时执行各自的清理部分。
由于我们要防止并发清理,因此我认为只有一个线程来运行所有清理任务(这将是清理服务)是有意义的。
下面的解决方案基于票证提供程序,该程序基本上创建了票证,并以它们创建的相同顺序将其放入清理队列。清理线程一直在等待“当前”票证;票证准备好清理后,将执行清理并处理下一张票证。
所有票证均已订购(根据其在清理服务中的插入顺序;并且还有一个编号)。
对于每个故障单/任务,我们可以使用CountDownLatch
,以便任何工作线程都可以向清理线程(使用故障单)发出主要任务的信号。
请参见下面的临时解决方案,
package stackOver;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SequentialCleanup {
// it is (obviously) important that this executor has one thread only
ExecutorService executorCleanup = Executors.newFixedThreadPool(1);
ExecutorService workerEs = Executors.newFixedThreadPool(20);
public static void main(String[] args) throws Exception {
SequentialCleanup o = new SequentialCleanup();
o.go();
}
private void go() throws InterruptedException, ExecutionException {
TicketProvider tp = new TicketProvider(executorCleanup);
EventListener el = new EventListener(tp, workerEs);
for (int i=0; i<20;i++) {
el.onEvent();
}
Thread.sleep(10_000L);
executorCleanup.shutdown();
workerEs.shutdown();
}
}
class EventListener {
private TicketProvider tp;
private ExecutorService workers;
public EventListener(TicketProvider tp, ExecutorService workers) {
this.tp = tp;
this.workers = workers;
}
public CompletionStage<Void> onEvent() {
Ticket ticket = tp.takeTicket();
return runAsyncCode().thenRun(
() -> {
// on finish, have the cleanup service run our cleanup
ticket.onWorkerFinish( ()-> {
// put cleanup code here
System.out.println("cleanups are orderer by ticket="+ticket);
});
});
}
private CompletionStage<Void> runAsyncCode() {
CompletableFuture<Void> res = new CompletableFuture<>();
workers.submit(
()-> {
System.out.println("doing some work..");
try { Thread.sleep(1000+(long)(Math.random()*1000)); } catch (Exception e) { }
System.out.println("done");
res.complete(null);
}
);
return res;
}
}
class Ticket {
private int number;
private CountDownLatch workerHasFinished = new CountDownLatch(1);
private volatile Runnable cleanup;
public Ticket(int number) {
this.number = number;
}
// after the worker has finished the main task, it calls onWorkerFinish()
public void onWorkerFinish(Runnable cleanup) {
this.cleanup = cleanup;
workerHasFinished.countDown();
}
// awaits end of the job, then cleans up
public void waitThenCleanup() {
try {
if (workerHasFinished.await(2000L, TimeUnit.MILLISECONDS)) {
System.out.println("cleanup ticket num=" + number);
cleanup.run();
System.out.println("end cleanup num=" + number);
}
else {
System.out.println("cleanup skipped for ticket=" + number + ", time elapsed");
}
} catch (Exception e) {
}
}
@Override
public String toString() {
return "Ticket["+number+"]";
}
}
class TicketProvider {
int ticketCounter = 0;
private ExecutorService esCleanup;
public TicketProvider(ExecutorService es) {
this.esCleanup = es;
}
public synchronized Ticket takeTicket() {
System.out.println("returning ticket " + ticketCounter);
Ticket ticket = new Ticket(ticketCounter++);
// enqueue for the cleanup
esCleanup.submit(ticket::waitThenCleanup);
return ticket;
}
}