跨两个ScheduledExecutorService任务池同步

时间:2014-08-14 11:39:40

标签: java multithreading

试图弄清楚如何在Java中正确地进行并发。我有两组任务。组内的任务不会相互干扰,因此可以同时运行而不会出现问题。但是,当第二组的任何任务正在运行时,不应运行一个组的任务。

第一组每10分钟运行一次。 第二组每小时运行一次。 我使用两个ScheduledExecutorService来安排这两个组。

理想情况下,每个任务都需要检查第二组中的任何任务是否正在运行,等待或完成,然后再运行。

我尝试用锁来解决这个问题,并想出了类似的东西:

    lock1.lock();
    try {
        while (!lock2.tryLock()) {
            wait();
        }

        for (int i = 0; i < 20; i++)
            System.out.println("Test1 @@@@@@@@@");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    finally {
        lock1.unlock();
    }
}

第一组共享lock1,第二组锁定2。但是这样,我强迫第一组的所有任务等待公共锁。

解决这个问题的优雅方法是什么?

2 个答案:

答案 0 :(得分:0)

尝试使用两个独立的Lock来完成这样的任务非常复杂且容易出错。您需要的内容与ReadWriteLock非常相似,其中两个Lock相互排斥。唯一的区别是两个锁应该是非排他性的,就像ReadLock一样。

这种锁定对可以直接实现,类似于ReentrantReadWriteLock的工作方式。下面的示例使用单个原子int状态值,该值表示可重入锁定计数与值的符号相结合,告知哪个组拥有锁定。与大多数锁实现一样,值为零表示锁是免费的。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class TwoGroupLock {

  private final Sync sync=new Sync();
  private final Lock group1, group2;

  public TwoGroupLock() {
    group1=new L(+1);
    group2=new L(-1);
  }

  public Lock getGroup1Lock() {
    return group1;
  }

  public Lock getGroup2Lock() {
    return group2;
  }

  private final class L implements Lock {
    private final int group;

    L(int i) { group=i; }
    public void lock() { sync.acquireShared(group); }
    public void lockInterruptibly() throws InterruptedException {
      sync.acquireSharedInterruptibly(group);
    }
    public boolean tryLock() { return sync.tryAcquireShared(group)>=0; }
    public boolean tryLock(long time, TimeUnit unit)
        throws InterruptedException {
      return sync.tryAcquireNanos(group, unit.toNanos(time));
    }
    public void unlock() { sync.releaseShared(group); }
    public Condition newCondition() {
      throw new UnsupportedOperationException();
    }
  }
  private final class Sync extends AbstractQueuedSynchronizer {
    protected @Override int tryAcquireShared(int group) {
      for(;;) {
        final int state=getState();
        if(state!=0 && (state^group)<0) return -1;
        if(compareAndSetState(state, state+group)) return +1;
      }
    }
    protected @Override boolean tryReleaseShared(int group) {
      for(;;) {
        final int state=getState();
        if(state==0 || (state^group)<0)
            throw new IllegalMonitorStateException();
        if(compareAndSetState(state, state-group)) return true;
      }
    }
  }
}

没有必要了解使用该类的所有内部细节。与ReadWriteLock类似,您有两种获取相关锁的方法,如ReadLock,多线程可以获取锁定,但前提是没有线程持有另一个锁。这两个锁是对称的。

答案 1 :(得分:0)

为此目的使用条件队列似乎是一个很好的主意,特别是在等待条件变为真或假时。

但是,我建议您使用Condition中的Lock#newCondition()个库。

以下是我头顶的一个例子:

public class Waiter {
    // Happens-before is strong enough to make sure the view
    // of running is visible to other threads
    // Even if the lock is not held, writes to a boolean
    // is atomic and non-compound, therefore, no synchronization
    // is needed because threads cannot be interleaved
    private boolean running = false;

    private final ReentrantLock taskLock = new ReentrantLock();
    private final Condition condition = this.taskLock.newCondition();

    public void check() throws InterruptedException {
        if (!this.taskLock.isLocked()) return;
        while (this.running) this.condition.await();
    }

    public void run() {
        this.taskLock.lock();
        this.running = true;
    }

    public void done() {          
        // Wait for the while loop to finish,
        // Release the threads held in the queue,
        // then the lock can be released            
        try {
            this.running = false;
            this.condition.signalAll();
        } finally {
            this.taskLock.unlock();
        }
    }
}

样本用法:

public static final Waiter WAITER = new Waiter();

// First task
WAITER.run();
try {
    // ...
} finally {
    WAITER.done();
}



// Second task
try {
    WAITER.check();
} catch (InterruptedException x) {
    Thread.currentThread().interrupt();
    x.printStackTrace();
}
// ...