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

时间:2017-08-30 21:28:13

标签: java multithreading sqlite javafx

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

但是我有一个很长的计算过程,由用户触发。此过程涉及在计算之间多次调用数据库。

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

这导致数据库文件被锁定的异常。 我希望有一种方法可以在发生这种情况时阻止该线程运行但是我无法在线找到任何明确的示例。他们中的大多数都过于简单了,我想要一种可扩展的方式。我尝试过使用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()。这将把状态设置为取消我正在我的calculateMethod()中检查这个,然后在返回之前将状态设置为停止(有效地结束线程)。

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

4 个答案:

答案 0 :(得分:3)

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

我会将所有数据库访问集中在一个单例类中,这样可以使最后一个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;
    }

}

答案 1 :(得分:0)

您的问题的解决方案可能Thead.stop(),已被弃用几个世纪以前(您可以在主题here上找到更多信息) 。

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

解决方案:

  • 使用calculationMethod支票填写isCancelled()
  • 尝试通过其他Thread打断下属操作。

第二种解决方案可能就是你要找的,但它取决于calculationMethod的实际代码(我猜你不能分享)。

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

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

修改

我没有看到你在编写答案时将数据库指定给SQLite。所以要为SQLite指定解决方案:

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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

  

WAL提供更多的并发性,因为读者不会阻止编写者和a   作家不会阻止读者。阅读和写作可以继续   同时进行。

https://sqlite.org/wal.html

您可能会对这些链接感兴趣:

https://stackoverflow.com/a/6654908/1989579 https://groups.google.com/forum/#!topic/sqlcipher/4pE_XAE14TY https://stackoverflow.com/a/16205732/1989579