延期锁定系统可确保订购

时间:2018-06-30 11:32:44

标签: java locking

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毫秒内没有锁定和解锁,请抛出异常(以表明我这方面存在某种编程错误)。
  • li>
  • 尝试重用过期票证(已使用或超时的票证)会引发异常

如果这不是Java内置的,是否有一种很好的方法来实现此目的,而不必每次增加服务数量时都唤醒所有阻塞的线程?

锁定机制的原理

我有一个事件侦听器,该侦听器必须按接收事件的顺序记录事件,但它必须在数据旁边记录来自异步源(例如网络)的相关信息。

1 个答案:

答案 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;
  }
}