简体   繁体   中英

How to kill old thread after using timertask and called new method?

Hi guys, I'm new to Java and I'm takin a course at Uni. I have gotten a task to write a small game that generates a random nr that the player will guess. After guessing the player is supposed to get the option to play again or WAIT and automatically come back to the main menu.

First I tried thread.sleep(5000) but it got stuck because it was waiting for user input (nextLine). Then a friend told be about timer and timertask, which i used and now my game is almost working.

The only problem is when i call a new method from my run() method the old (play again Y/N) thread running in the background is not ended. So when my menu appears after 5 seconds, my first input is connected to the play again Y/N choice and not the main menu options. Here are the kod parts:

public void tryAgain() {
    Timer timer = new Timer();
    Task timerTask = new Task();
    int y = 1;
    String yesNo = sc.nextLine();
    System.out.println("Try again Y/N");
    Statistics.setGames(games);
    timer.schedule(timerTask, 5000);

    do {
        try {

            yesNo = sc.nextLine();
            if (yesNo.equals("Y") || yesNo.equals("y")) {
                guesses = 0;
                y = 2;
                timerTask.cancel();
                playGame();


            } else if (yesNo.equals("N") || yesNo.equals("n")) {
                y = 3;
                timerTask.cancel();
                Statistics.setGames(games);
                menu.showMainMenu();

            } else {
                System.out.println("Wrong input, try Y or N:");

            }

        } catch (Exception e) {
            sc.next();

            System.out.println("Wrong input, try Y or N:");

        }

    } while (y == 1);

}

and :

import java.util.TimerTask;

class Task extends TimerTask {

    play menu = new play();

    public void run() {

        Statistics.getGames();

        menu.menu.showMainMenu();

        cancel();

    }

}

You cannot interrupt blocking reads. But you can use the BufferedReader.ready() method, which will tell us whether the underlying stream is ready to be read.

Let's implement a non blocking reader:

public class OurNonBlockingConsoleReader implements Callable<String> {

    public String call() throws IOException {
        BufferedReader sysInReader = new BufferedReader(new InputStreamReader(System.in));
        String answer;
        do {
            System.out.println("Try again Y/N");
            try {
                while (!sysInReader.ready()) {
                    Thread.sleep(100);
                }
                answer = sysInReader.readLine();
            } catch (InterruptedException e) {
                return null;
            }
        } while (!"y".equalsIgnoreCase(answer) && !"n".equalsIgnoreCase(answer));
        return answer;
    }
}

Next, we call this reader with a timeout, using the ExecutorService and the Future from java.util.concurrent package:

public void tryAgain() throws InterruptedException, ExecutionException {
    ExecutorService readerExecutor = Executors.newSingleThreadExecutor();
    Future<String> readerResult = readerExecutor.submit(new OurNonBlockingConsoleReader());
    try {
        String answer = readerResult.get(5, TimeUnit.SECONDS);
        if ("y".equalsIgnoreCase(answer)) {
            playTheGame();
        } else {
            goToMainTheMenu();
        }
    } catch (TimeoutException e) {
        goToMainTheMenu();
    } finally {
        readerExecutor.shutdownNow();
    }
}

The readerResult.get(...) call will wait 5 seconds for a valid answer. It there is no valid answer returned from OurNonBlockingConsoleReader.call , the Future.get will raise a TimeoutException .

Addressing only your immediate problem:

Your run method needs to set y to something other than 1 (perhaps 0), then call interrupt on Y/N thread. This will kick it out of a blocking read with an InterruptException or ClosedByInterruptException. (Your catch block will need to be smarter.) Then the Y/N loop will finish because y is not 1. End of problem.

For this to work, y needs to be declared volatile , or each thread might use its own copy. (Accessing it only within synchronized blocks will work also.)

Added Example:

public class YesNo  {
    private volatile Thread  tryAgainThread;
    private volatile int     y = 1;

    public doNotTryAgain()  {
        y = 0;
        tryAgainThread.interrupt();
    }
    //  Called only from tryAgainThread thread.
    public void tryAgain()  {
        do  {
            try {
                // Exactly what you have now.
                ...
            }
            catch (Exception e)  {}
        }  while (y == 1);
    }
....

class Task extends TimerTask  {
    public YesNo  ynInstance;
    ...
    public void run()  {
        ynInstance.doNotTryAgain();
        Statistics.getGames();
        ...
    }
}

I'll let you figure out how to set tryAgainThread , which is the thread the tryAgain method is called--and is looping--on. Also, Task needs to know the relevant (and probably only) instance of the class that contains the tryAgain call running in the 'tryAgainThread'. In your case some static public fields will do the job, but ideally you'd want something more elegant.

Also, catch (Exception e) {} will work fine, but ideally you'd check your exceptions better.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM