杰瑞科技汇

Java如何安全停止线程?

Of course. Stopping a thread in Java is a crucial topic with a very important history. Let's break it down from the old, dangerous way to the modern, correct way.

The Short Answer (The Modern, Correct Way)

Do not use Thread.stop(). It is deprecated and dangerous. It can leave your object in an inconsistent, corrupted state.

Instead, use a cooperative cancellation approach with a volatile boolean flag.


The Old, Dangerous Way: Thread.stop() (Why You MUST Avoid It)

In early versions of Java, Thread.stop() was the method to kill a thread. It was removed because it's fundamentally flawed.

The Problem: Thread.stop() acts like an emergency brake. It immediately throws a ThreadDeath error at whatever point the thread is executing its code.

// DANGEROUS - DO NOT USE
myThread.stop(); // Immediately throws ThreadDeath in the thread

This is catastrophic because:

  • No Cleanup: If the thread was in the middle of writing to a file or a network socket, it's stopped mid-operation, leaving the resource in an unknown state.
  • Corrupted Objects: If the thread was holding a lock on an object and is killed, the lock is released, but the object's internal state (e.g., a counter, a list) might be halfway through an update. Other threads can now see this corrupted object, leading to unpredictable behavior and very hard-to-find bugs.
  • Uncaught Exception: ThreadDeath is an error, not an exception. If you don't have a specific catch block for it (which you almost never should), it can propagate up and cause other issues.

Because of these risks, Thread.stop() was deprecated in Java 1.2 and has remained so ever since.


The Modern, Correct Way: Cooperative Cancellation

The modern approach is to make the thread responsible for its own lifecycle. The thread periodically checks a "cancellation request" flag and gracefully shuts itself down when the flag is set.

This is called cooperative cancellation because the thread cooperates with the code that wants to stop it.

Step-by-Step Implementation

Step 1: Create a Cancellation Flag

Use a volatile boolean variable. The volatile keyword is essential. It ensures that every thread sees the most up-to-date value of the flag, preventing a situation where the thread checking the flag is looking at a stale, cached copy from its own memory.

private volatile boolean running = true;

Step 2: The Thread's Run Logic

The thread's run method should contain a loop that periodically checks the running flag. When the flag becomes false, the thread should clean up any resources and exit its run method.

Step 3: Requesting Cancellation

From another thread (e.g., your main thread), simply set the flag to false.

Complete Example

Here is a complete, runnable example.

public class StoppableTask implements Runnable {
    // The volatile flag that controls the thread's execution
    private volatile boolean running = true;
    @Override
    public void run() {
        int counter = 0;
        // Loop as long as the 'running' flag is true
        while (running) {
            try {
                System.out.println("Working... " + counter++);
                // Simulate doing some work
                Thread.sleep(500); 
            } catch (InterruptedException e) {
                // This is another way a thread can be stopped, more on this later.
                System.out.println("Thread was interrupted.");
                // We can also check the flag here
                if (!running) {
                    System.out.println("Interruption received while not running, exiting.");
                }
                break; // Exit the loop
            }
        }
        System.out.println("Task finished gracefully.");
    }
    // The public method to request the thread to stop
    public void stop() {
        this.running = false;
        System.out.println("Stop signal sent.");
    }
    public static void main(String[] args) throws InterruptedException {
        StoppableTask task = new StoppableTask();
        Thread workerThread = new Thread(task);
        workerThread.start(); // Start the thread
        // Let it run for a few seconds
        Thread.sleep(3000); 
        // Now, request the thread to stop cooperatively
        task.stop(); 
        // Wait for the thread to finish its cleanup
        workerThread.join(); 
        System.out.println("Main thread finished.");
    }
}

Output:

Working... 0
Working... 1
Working... 2
Working... 3
Working... 4
Working... 5
Stop signal sent.
Task finished gracefully.
Main thread finished.

Advanced Cancellation with ExecutorService

When using ExecutorService (which is the recommended way to manage threads), you don't interact with the Thread object directly. Instead, you submit tasks and use a Future object to manage them.

The Future.cancel(true) method is the modern equivalent.

  • future.cancel(true): Interrupts the thread if it's running.
  • future.cancel(false): Does not interrupt the thread. It will only stop the thread if it's not currently running (e.g., waiting in a queue).

Example with ExecutorService

import java.util.concurrent.*;
public class ExecutorServiceStopExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // Submit a long-running task
        Future<?> future = executor.submit(() -> {
            int counter = 0;
            while (!Thread.currentThread().isInterrupted()) { // Check for interruption
                System.out.println("Working... " + counter++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // This is the key: when interrupted, the sleep throws an exception.
                    // We catch it, set the interrupt status again, and break the loop.
                    System.out.println("Task was interrupted during sleep.");
                    Thread.currentThread().interrupt(); // Restore the interrupt status
                    break;
                }
            }
            System.out.println("Task finished gracefully after interruption.");
        });
        // Let it run for a bit
        Thread.sleep(3000);
        // Cancel the task. This will interrupt the underlying thread.
        System.out.println("Cancelling the task...");
        future.cancel(true); 
        // Wait for the executor to shut down completely
        executor.shutdown();
        System.out.println("Main thread finished.");
    }
}

How it works:

  1. future.cancel(true) is called.
  2. The ExecutorService sees this and calls thread.interrupt() on the thread running the task.
  3. The thread is inside Thread.sleep(). An interrupt during a blocking operation like sleep(), wait(), or join() immediately throws an InterruptedException.
  4. The task catches the exception, prints a message, and breaks out of the loop, thus stopping gracefully.

Summary: Best Practices

Method How it Works Safety Recommendation
Thread.stop() Throws ThreadDeath error. Very Unsafe. Corrupts objects. NEVER USE. Deprecated.
volatile Flag Thread checks a flag periodically. Safe. Allows for cleanup. Standard for simple loops.
Future.cancel(true) Interrupts the thread (thread.interrupt()). Safe. Best for ExecutorService. Recommended for modern, concurrent code.
thread.interrupt() Directly sets the thread's interrupt status. Safe. Requires the task to check isInterrupted(). Use this if you have direct access to the Thread object and want to interrupt it.
分享:
扫描分享到社交APP
上一篇
下一篇