用Java管理访问数据库的线程



我正在开发一个访问SQLite数据库的应用程序。问题是当有查询时数据库会被锁定。大多数时候这不是问题,因为应用程序的流是线性的。

然而,我有一个非常长的计算过程,这是由用户触发的。这个过程包括在两次计算之间多次调用数据库。

我想让用户获得一些视觉反馈,所以我一直在使用Javafx-progressIndicator和Javafx.courrency框架中的服务。问题是,这让用户可以在应用程序中自由移动,并可能触发对数据库的其他调用。

这导致数据库文件被锁定的异常。当这种情况发生时,我想找到一种方法来阻止该线程运行,但我在网上找不到任何明确的例子。它们中的大多数都过于简单化了,我想要一种可扩展的方式。我尝试过使用cancel((方法,但这并不能保证线程会被及时取消。

因为我无法检查isCancelled的所有代码部分,所以有时线程被取消的时间和它有效停止的时间之间会有延迟。

所以我想到了下面的解决方案,但我想知道是否有更好的方法来提高效率,避免比赛条件和上吊。

// Start service
final CalculatorService calculatorService = new CalculatorService();
// Register service with thread manager
threadManager.registerService(CalculatorService);
// Show the progress indicator only when the service is running
progressIndicator.visibleProperty().bind(calculatorService.runningProperty());
calculatorService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent workerStateEvent) {
System.out.println("SUCCEEDED");
calculatorService.setStopped(true);
}
});
// If something goes wrong display message
calculatorService.setOnFailed(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent workerStateEvent) {
System.out.println("FAILED");
calculatorService.setStopped(true);
}
});
// Restart the service
calculatorService.restart(); 

这是我的服务类,我已经将其子类化,以包括可以用于设置服务状态(已停止或未停止(的方法

public class CalculatorService extends Service implements CustomService {
private AtomicBoolean stopped;
private CalculatorService serviceInstance;
public FindBundleService() {
stopped = new AtomicBoolean(false);
instance = this;
}
@Override
protected Task<Results> createTask() {
return new Task<Result>() {
@Override
protected Result call() throws Exception {
try {
Result = calculationMethod(this, serviceInstance);
return Result;
} catch (Exception ex) {
// If the thread is interrupted return
setStopped(true);
return null;
}
}
};
}
@Override
public boolean isStopped() {
return stopped.get();
}
@Override
public void setStopped(boolean stopped) {
this.stopped.set(stopped);
}
}

该服务实现了我定义的接口

public interface CustomService {
/**
* Method to check if a service has been stopped
* 
* @return
*/
public boolean isStopped();
/**
* Method to set a service as stopped
* 
* @param stopped
*/
public void setStopped(boolean stopped);
}

所有服务都必须向线程管理器注册,线程管理器是一个单例类。

public class ThreadManager {
private ArrayList<CustomService> services;
/**
* Constructor
*/
public ThreadManager() {
services = new ArrayList<CustomService>();
}
/**
* Method to cancel running services
*/
public boolean cancelServices() {
for(CustomService service : services) {
if(service.isRunning()) {
((Service) service).cancel();
while(!service.isStopped()) {
// Wait for it to stop
}
}
}
return true;
}

/**
* Method to register a service
*/
public void registerService(CustomService service) {
services.add(service);
}
/**
* Method to remove a service
*/
public void removeService(CustomService service) {
services.remove(service);
}
}

在应用程序中的任何位置,如果我们想停止我们称之为cancelServices((的服务。这将把状态设置为cancelled。我在calculationMethod((中检查了这一点,然后在返回之前将状态设置为stop(有效地结束了线程(。

if(task.isCancelled()) {
service.setStopped(true);
return null;
}

(我假设您正在使用JDBC进行数据库查询,并且您可以控制运行查询的代码(

我会将所有数据库访问集中在一个singleton类中,这将使最后一个PreparedStatement在单个线程ExecutorService中运行当前查询。然后,您可以询问该单例实例,如isQueryRunning()runQuery()cancelQuery(),它们将是synchronized,这样您就可以决定在应该取消计算时向用户显示一条消息,取消计算并开始新的计算。

类似(添加空检查和catch (SQLException e)块(:

public class DB {
private Connection cnx;
private PreparedStatement lastQuery = null;
private ExecutorService exec = Executors.newSingleThreadExecutor(); // So you execute only one query at a time
public synchronized boolean isQueryRunning() {
return lastQuery != null;
}
public synchronized Future<ResultSet> runQuery(String query) {
// You might want to throw an Exception here if lastQuery is not null (i.e. a query is running)
lastQuery = cnx.preparedStatement(query);
return exec.submit(new Callable<ResultSet>() {
public ResultSet call() {
try {
return lastQuery.executeQuery();
} finally { // Close the statement after the query has finished and return it to null, synchronizing
synchronized (DB.this) {
lastQuery.close();
lastQuery = null;
}
}
}
// Or wrap the above Future<ResultSet> so that Future.cancel() will actually cancel the query
}
public synchronized void cancelQuery() {
lastQuery.cancel(); // I hope SQLite supports this
lastQuery.close();
lastQuery = null;
}
}

您的问题的解决方案可以是Thead.stop(),它在几个世纪前就已被弃用(您可以在此处找到有关该主题的更多信息(。

为了实现类似的行为,建议使用Thread.interrupt(),它(在Task的上下文中(与Task.cancel()相同。

解决方案:

  • isCancelled()检查填充calculationMethod
  • 尝试通过其他Thread中断下属的操作

第二个解决方案可能是您想要的,但它取决于calculationMethod的实际代码(我想您无法共享(。

终止长数据库操作的一般示例(所有这些都是从另一个线程执行的(:

  • 终止与数据库的连接(假设数据库足够智能,可以终止断开连接时的操作,然后解锁数据库(
  • 请求数据库终止操作(例如kill <SPID>(

编辑:

当我写答案的时候,我没有看到你把数据库指定为SQLite。因此,要指定SQLite的解决方案:

  • 终止连接没有帮助
  • 在您的javaSQLite接口中查找sqlite3_interrupt的等效项

也许你可以调用线程实例t1,t1.interrupt((方法,然后在线程的run方法(也许是calculationMethod(中,添加一个条件语句。

public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// my code goes here
} catch (IOException ex) {
log.error(ex,ex)
}
}
}

使用WAL模式(预写日志记录(,您可以与sqlite数据库并行执行许多查询

WAL提供了更多的并发性,因为读取器不阻塞写入程序编写器不会阻止读者。阅读和写作可以继续同时

https://sqlite.org/wal.html

也许你对这些链接感兴趣:

https://stackoverflow.com/a/6654908/1989579https://groups.google.com/forum/#!主题/sqlcipher/4pE_XAE14TYhttps://stackoverflow.com/a/16205732/1989579

最新更新