我有一个实用程序
1) loads mapping from the file
2) expose api where user can send the file
我想要实现的是在文件更改时重新加载映射文件,因此不需要重新启动应用程序。但是,当检测到文件更改时,我希望完成所有操作(发送数据),然后重新加载映射。反之亦然,在更新映射时,所有 SEND 请求都应等待。到目前为止,我想出了这个:
改变文件的FileWatcher
public class MapperDetector {
private static final Logger logger = LogManager.getLogger(MapperDetector.class);
private final WatchService watcher;
private final SendDataUnit server;
private final Condition condition;
private final Lock lock;
private final Condition isUpdatingFileCondition;
private Path dirPath;
private String fileToWatch;
private Thread worker;
public MapperDetector(SendDataUnit server,Lock lock, Condition condition, Condition isUpdatingMapping) throws IOException {
this.server = server;
this.condition = condition;
this.lock = lock;
this.isUpdatingFileCondition = isUpdatingFileCondition;
this.watcher = FileSystems.getDefault().newWatchService();
}
@Override
public void setFileToWatch(Path fileToWatch) {
this.dirPath = Paths.get(fileToWatch.toUri()).getParent();
if (!Files.isDirectory(dirPath)) {
throw new RuntimeException("incorrect monitoring folder: " + dirPath.toString());
}
this.fileToWatch = Paths.get(fileToWatch.toUri()).getFileName().toString();
if (!Files.exists(fileToWatch)) {
throw new RuntimeException("incorrect monitoring file: " + fileToWatch);
}
}
@Override
public void watch() throws IOException, DistCpServerException {
server.reloadMapping(getWatchedFile());
dirPath.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
this.worker = new Thread(this::run);
worker.start();
}
@Override
public String getWatchedFile() {
return dirPath.resolve(fileToWatch).toString();
}
private void run() {
try {
logger.info("Starting the monitoring of " + fileToWatch);
WatchKey key;
while ((key = watcher.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
String filename = ev.context().toString().replace("~", "");
if (filename.equals(fileToWatch)) {
reloadFile();
}
}
key.reset();
}
} catch (Exception e) {
logger.info("Watcher inrerrupted ", e);
}
}
private void reloadFile() throws InterruptedException, DistCpServerException {
lock.lock();
try {
while (server.isSendingData()) {
condition.await();
}
server.reloadMapping(getWatchedFile())
isUpdatingFileCondition.signalAll();
} finally {
lock.unlock();
}
}
}
这是一个在线程中运行的简单文件观察器,如果检测到文件发生变化,它会等待直到所有复制数据完成,然后重新加载映射文件。完成后,它会发出重新加载完成的信号。
public class SendDataUnit {
private static final Logger logger = LogManager.getLogger(SendDataUnit.class);
private static int inProgress = 0;
public static AtomicBoolean isUpdatingFile = new AtomicBoolean(false);
private final static Lock lock = new ReentrantLock();
private final static Condition shouldUpdateFileCondition = lock.newCondition();
private final static Condition isUpdatingFileCondition = lock.newCondition();
private final MapperDetector mappingWatcher;
// progress indicates data should be send
public static void addProgress() {
try {
lock.lock();
inProgress++;
} finally {
lock.unlock();
}
}
// data was sent, we can remove progress
public static void removeProgress() {
try {
lock.lock();
inProgress--;
if (inProgress == 0) {
shouldUpdateFileCondition.signalAll();
}
} finally {
lock.unlock();
}
}
// method that waits until mapping file is reloaded
public static void waitForMappingFileUpdate() throws InterruptedException {
try {
lock.lock();
while (isUpdatingFile.get()) {
isUpdatingFileCondition.await();
}
} finally {
lock.unlock();
}
}
public boolean isSendingData() {
return inProgress != 0;
}
public static void setIsUpdatingFile(Boolean value) {
isUpdatingFile.set(value);
}
public SendDataUnit () {
this.mappingWatcher = new MapperDetector (this, this::readMappingFile, lock, shouldUpdateFileCondition, isUpdatingFileCondition);
mappingWatcher.setFileToWatch(Paths.get(System.getProperty("mapping.file")));
mappingWatcher.watch();
}
// method that reloads the file
public void reloadMapping(String mappingFile) {
try {
isUpdatingFile.set(true);
setMapping(FileUtils.readConfigFileFromLocal(mappingFile));
isUpdatingFile.set(false);
} catch (IOException e) {
throw new DistCpServerException("Setting mapping file failed", e);
}
}
// method that sends data
public void sendData(..){...}
}
最后我有操作它的服务</p>
// this class is called inREST API
public class DataSenderService(){
private DataProcesser processer; // this DataProcesser has nested SendDataUnit instance in it.
public void sendData(List<String> dataSets){
DistCpServerImpl.waitForMappingFileUpdate();
DistCpServerImpl.addProgress();
for (String p : dataSets) {
processer.sendData(...);
}
DistCpServerImpl.removeProgress();
}
}
这似乎有效,但是我不确定在此示例中对多个条件变量使用一个锁是否正确,并且不会导致死锁或任何不需要的行为。由于我从 REST API 调用服务,因此应该产生不同的线程来处理每个请求,因此我需要使用某种并发管理。这是如何在一个锁上使用多个条件变量的正确方法还是我需要创建两个锁,每个条件变量 1 个?
感谢您的帮助!