挑剔的主机(锁定?)



我相信无论使用什么语言都可以考虑我的问题,但为了有一些"锚",我将使用Java语言来描述它。

让我们考虑以下场景:我有一个扩展Thread的类PickyHost,它的一个实例pickyHostInst正在运行。这个类可能看起来像这样:

class PickyHost extends Thread {
private ArrayList<Guest> guests;
public void enter(Guest g) {
// deal with g
}
private void pickGuests() {
// ...
}
public void run() {
// listen indefinitely
}
}

此外,在后台,我有许多Guest实例在运行(它们还扩展了Thread类),偶尔,一些GuestickyHostInst上的enter方法,参数g本身就是。现在,我希望PickyHost在以下意义上挑剔:

在有人调用enter方法后,它会立即将g放在guests列表的末尾,并强制g等待通知。此外(我认为问题的关键在于此),它会自己睡5秒钟,并以某种方式允许(在这5秒钟内)其他客人调用enter方法(如果发生这种情况,它就会忘记必须睡多久,并将闹钟重置为再次睡5秒钟)-我称之为敏感睡眠

正如你所看到的,如果有很多客人到达,pickyHostInst的睡眠总时间可能会很长,比如:A到达,4秒后B到达,然后4秒后C到达,以此类推。然而,假设已经创建了一个链A,B。。。,客人的G,从G到达的那一刻到5秒后,没有人到达。然后,我希望pickyHostInst调用pickGuests方法,该方法使用某种算法确定{a,B,…,G}的子集S,以通知他们可以停止等待并继续执行正常操作,此外还从来宾列表中删除S的元素。方法pickGuests可能需要一些时间才能完成,同时一些来宾H可能已经到达并调用了enter-然后enter应该正常进行,但pickGuests该忽略H,并在其最后一次调用结束时处理{A,B,…,G}-而不是{A,B,…,G,H}。在完成pickGuests后,pickyHostInst应该(这里我有两个想法-实施其中任何一个都会让我高兴:)任一

  1. 再次进入5秒的敏感睡眠,之后,如果H之后没有客人到达,则再次调用pickGuests,或者
  2. 像往常一样通过enter方法同时为来宾提供服务,但仅在之后调用pickGuestsmax("来自S的最后一位客人(来自上次调用)通知pickyHostInst的时刻(例如:S中的最后一个"谢谢,Host先生")","最后一位(最新)客人调用enter后5秒的时刻")

最后,经过长时间的介绍,我的问题是——我需要哪些工具来完成这样的任务?不幸的是,我有点迷失在各种锁和多线程/锁定机制的丰富性中,无法辨别哪一个适合我的问题(或者哪一个以某种方式组合在一起)。

我将非常感谢一些能让我走上正轨的代码草图。

您可以使用java.util.Timer对象,该对象可以在enter方法中重置。计时器任务将在自己的线程中运行,如果没有提前取消,它将为您进行挑选。

请注意,enter方法将在众多Guest线程中的一个线程上运行。这意味着它可能应该同步。最简单的方法是将synchronized关键字添加到Java中的方法声明中:public synchronized void enter(Guest g)。这将确保一次只能有一位客人进入。您可以在此处输入计时器取消/重新启动代码。

java.util.Timer通过抽象的java.util.TimerTask类进行处理的方式。这是一种类型的Runnable,它也有一个取消任务的方法。我的建议是安排一项任务,每当客人进入时,每隔5000毫秒就会挑选客人。如果上一个来宾的任务正在运行,请先取消它。

enter方法应该获取访客的锁(使用同步块)并让访客等待。挑选应该对您选择的客人调用notify()方法。这将允许它们继续执行。

当您从队列中删除选定的来宾时,请注意Java集合默认情况下不是线程安全的。您必须使用外部锁来确保在添加和删除客人时没有其他人修改您的列表。Collections.synchronizedList(List)方法提供了一种方便的方法。

以下是讨论我提到的主题的链接列表:

  1. http://docs.oracle.com/javase/tutorial/essential/concurrency/(适合初学者的优秀教程)
  2. http://docs.oracle.com/javase/7/docs/api/java/util/Timer.html
  3. http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
  4. http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedList%28java.util.List%29

我可能会这样做。我会尽量避免notify/notifyAll,因为你将不得不涉及一个标志,因为虚假的唤醒和混乱的代码相当多。CountDownLatch在这里是一个更好的选择IMO,尽管这个名字有点奇怪。

static final long five_sec = TimeUnit.SECOND.toNanos(5)
final Queue<Pair<Guest, CountDownLatch>> guests = new LinkedList<>();
long earliest = -1;
// Synchronizing on "this" is fine but using private lock 
// object is even better
final Object lock = new Object();

void enter(Guest g){
Pair p = Pair.of(g, new CountDownLatch(1));
synchronized(lock){
guests.get().add(p);
earliest = System.nanoTime() + five_sec;
}
p.second.await();
}
void pickGuests(){
synchronized(lock){
// pop a few guests from sofar and wake them
Guest g = sofar.poll();
if(g != null){
g.second.countDown();
}
}
}
void run(){
while(!Thread.currentThread().isInterrupted()){
long sleepTime;
synchronized(lock){
if(System.nanoTime() > earliest){
pickGuests();
}
sleepTime = earliest - System.nanoTime();
sleepTime = sleepTime < five_sec ? five_sec : sleepTime;
}
Thread.sleep(sleepTime);
}
}

最新更新