8

This may be a case of me just misunderstanding what I've read, but all the examples for killing a thread in Java seem to indicate that you have to signal a thread to kill itself; you can't kill it from the outside without some serious risks. The problem is, all the examples of how to "politely" ask a thread to die have some kind of looping so all you have to do is watch a flag on every iteration.

So, what I've got is a thread that does something that just takes a while (a series of SQL queries). It's certainly possible for me to just have a check after each step, but they aren't in a loop and there isn't a very elegant way that I'm aware of to get around that. Here's an example of what I'm doing:

new Thread(new Runnable(){
    public void run(){
        //query 1
        Connection conn = db.getConnection();
        Statement s = conn.createStatement();
        ResultSet rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }

        //query 2
        rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }

        //query 3
        rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }
    }
}).start();

This is an example, I don't use anonymous inner classes but it illustrates that my run() method can't elegantly stop itself. Futhermore, even I check after each step, if a particular query takes a very long time to run, this code would not be able to stop until after the query was complete.

This code is for a GUI application, and I would really like to find a good way to kill a thread quickly without using Thread.stop().

EDIT - yshavit's answer was a big help, as I wasn't aware that Statement.cancel() existed. If you're curious, the answer to my particular problem was to build out a more abstracted database access class. The class had to create a child thread to execute the query and loop while it was running, checking on every iteration if the current thread (not the child) was interrupted. If it does get interrupted, it just calls Statement.cancel() and the child thread will throw an exception and die. Not all JDBC drivers support Statement.cancel(), but Oracle 11g does.

monitorjbl
  • 4,280
  • 3
  • 36
  • 45
  • possible duplicate of [How to abort a thread in a fast and clean way in java?](http://stackoverflow.com/questions/94011/how-to-abort-a-thread-in-a-fast-and-clean-way-in-java) – Brian Roach Feb 29 '12 at 02:32
  • You need to set a volatile boolean and check it, or check to see if the thread has been interrupted (and interrupt it from another thread to cause it to shut down). – Brian Roach Feb 29 '12 at 02:33

5 Answers5

4

Find out what takes a while, and cancel it. If the thing that takes the most time are the rs.next() loops, you can do:

while(rs.next()){
    if (myVolatileBooleanSaysToStop) {
        return; // or whatever
    }
    //do stuff
}

If the thing that takes a while are the statements, and your JDBC driver/server support Statement.cancel, then you can publish your Statement to a second thread which is responsible for calling Statement.cancel as appropriate. I'm not sure, but I think that'll result in a SQLException from the driver, which you can then somehow identify as having come from the cancellation, and handle accordingly.

Also, you should consider refactoring a bit. You have three chunks of "run a query, iterate over its results" that could be factored into a method (which would then take care of closing the statement, etc).

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • A lot of database drivers won't be thread safe for Statement and ResultSet objects, even though the JDBC spec says they should be. – Steven Schlansker Feb 29 '12 at 02:42
  • This is what I was trying to avoid, it's just so ugly looking to add that check every time I iterate. Plus, it really doesn't get around that long-running query issue. The ResultSet is only available _after_ the query finishes, and I unfortunately do not have the option of improving the database structure to improve the queries. – monitorjbl Feb 29 '12 at 02:43
  • @monitorjbl regarding the ugliness, there's not really an alternative. That's why I suggested refactoring the code -- it consolidates the ugliness. As for the query itself taking a long time, if you can't cancel it because of the driver, there's not a whole lot you can do from your app. – yshavit Feb 29 '12 at 02:47
  • @yshavit My actual code has separate methods to process these, but its not able to be consolidated easily. I may have to just make my own DAO; I wasn't planning on building infrastructure like that because this code wouldn't be re-used much but I guess this is a good excuse to consolidate everything. Thanks for your advice! – monitorjbl Feb 29 '12 at 02:54
  • This doesn't answer the question. It takes a long time to execute the queries, not to read the results. – gordy Feb 29 '12 at 03:06
  • @gordy Well firstly, I gave some info on how to cancel the query in some cases. And depending on the query and what you're doing with the results, the loops could absolutely take a long time. – yshavit Feb 29 '12 at 04:34
  • 1
    @gordy You have to fire off a child thread to execute the query with the Statement. Then, its parent simply loops while the child is still alive, monitoring for Thread.isInterrupted() to be true. If it encounters that, all it has to do is run Statement.cancel(). I can verify that this does indeed work with the Oracle 11g JDBC driver, but YMMV. – monitorjbl Feb 29 '12 at 19:33
1

To interrupt a thread the client (ie code running outside of the thread):

threadInstance.interrupt();

To check to see if the thread your code is running on has been interrupted:

Thread.currentThread().isInterrupted()

Another option is to try and sleep:

Thread.currentThread().sleep(1)

What's nice about sleeping is it will throw an exception if the thread has been interrupted (ie InterruptedException) That's why not ignoring those exceptions is important.

You could add checks inside your while loops using Thread.currentThread().isInterrupted() or you could check for a sleep(1) between statements. I wouldn't sleep in the loop as that will really slow your code down. This is where a hybrid approach might be best:

if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();

Then you catch that at the bounds of your run() method and stop.

The point is you have to periodically check to see if an outside client has requested you shutdown. If you had 6 statements in a thread. Putting checks in between those statements would allow your thread to quit.

public run() {
    try {
       doSomething();
       if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
       doNextSomething();
       if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
       doSomeMoreThings();
       if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
       doYetMoreThings();
    } catch( InterruptedException e ) {
       System.out.println("Duff man going down.");
    }
}

There's really no difference between doing that and putting a single check in a loop.

chubbsondubs
  • 37,646
  • 24
  • 106
  • 138
0

If you don't want to implement your own thread.kill() mechanism from the scratch, you can use existing API, Manage your thread creation within a ThreadPoolExecutor, and use Future.cancel() to kill the running thread:

ThreadPoolExecutor threadPoolExecutor = Executors.newSingleThreadExecutor();
Runnable longRunningTask = new Runnable();

// submit task to threadpool:
Future longRunningTaskFuture = threadPoolExecutor.submit(longRunningTask);

... ...
// At some point in the future, if you want to kill the task:
longRunningTaskFuture.cancel(true);
... ...

Cancel method will behaviour differently based on your task running state, check the API for more details.

yorkw
  • 40,926
  • 10
  • 117
  • 130
0

If you don't want to interrupt the thread the only other option is to somehow kill/cancel the long running query. If you could start the query async you could just wait until the results are ready or you receive a signal to die but you cannot call jdbc async.

Community
  • 1
  • 1
gordy
  • 9,360
  • 1
  • 31
  • 43
-1

A Java thread will close itself once run() finishes. If you have run() in a loop, break out of the loop, Thread.run will end, and the thread will die. You can also use return; if I'm not mistaken.