2017-08-30 82 views
9

我正在研究訪問SQLite數據庫的應用程序。問題是數據庫在查詢時被鎖定。大多數情況下,這不是問題,因爲應用程序的流程非常線性。管理使用Java訪問數據庫的線程

但是我有一個很長的計算過程,這是由用戶觸發的。該過程涉及在計算之間對數據庫的多次調用。

我希望用戶獲得一些視覺反饋,所以我一直在使用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()。這將設置狀態爲取消我正在檢查我的計算方法()然後設置狀態停止之前返回(有效地結束線程)。

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

的分貝爲什麼多個呼叫?爲什麼不能在一次通話中獲得所有你需要的信息? – Sedrick

+0

那是因爲我們正在根據從數據庫收到的內容進行一些計算,然後使用結果從數據庫中獲取其他內容。它是我不能改變的商業邏輯。 – user3552551

+1

我會禁用所有'節點',這將觸發另一個調用數據庫,直到'任務'完成。 – Sedrick

回答

0

一個解決問題的方法可能Thead.stop(),這已棄用百年前(你可以找到更多的話題here)。

爲了實現類似的行爲,建議使用Thread.interrupt(),這是(在Task的上下文中)與Task.cancel()相同。

解決方案:

  • 填寫您的calculationMethodisCancelled()檢查。
  • 嘗試通過其他Thread中斷下operation操作。

第二個解決方案可能是你在找什麼,但它取決於calculationMethod(我猜你不能共享)的實際代碼。

殺死長的數據庫操作通用的例子(所有這一切都從另一個線程執行):

  • 殺死到數據庫的連接(假設數據庫足夠聰明,不會扼殺在斷開連接,然後運行解鎖數據庫)。
  • 要求數據庫終止操作(例如,kill <SPID>)。

編輯:

我沒有看到你所指定的數據庫對於SQLite時,我寫我的回答這個問題。因此,要指定SQLite的解決方案:

3

(我假設你正在使用JDBC爲您數據庫查詢,並且你可以控制運行查詢的代碼)

我會集中所有數據庫訪問的單例類,這將保持運行當前查詢的最後一個PreparedStatement在一個單一的e線程ExecutorService。然後,你可以問這個單例實例,例如isQueryRunning(),runQuery(),cancelQuery()這將是​​,所以你可以決定每當計算被取消,取消它並開始一個新的消息給用戶。

喜歡的東西(加上null檢查和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; 
    } 

} 
0

也許你可以調用線程實例T1,t1.interrupt()方法,然後在線程(也許calculationMethod)的運行方法,添加一個條件語句。

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