十分钟搞定Java多线程-如何终止Java线程

巧了我就是萌 提交于 2020-04-28 02:17:15

这是基础的Java线程知识,也是Java面试中经常出现的题,实际上并没有表面看上去那么简单。

启动Java线程容易,因为有Thread.start()方法,但是终止线程可不容易,因为没有可安全终止线程的方法。虽然Thread类提供了一个stop()方法,不过该方法在Java发布第一个版本后就被废弃(Deprecated)了。

    @Deprecated(since="1.2")
    public final void stop() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

在现在的Java版本中,可以通过使用boolean volatile变量来终止线程

Java的线程从run()方法开始执行,当执行完任务或者遇到异常时,就会跳出run()方法,此时线程终止。那我们是不是可以利用这个特性来终止线程,通过创建一个boolean变量b,线程在每次迭代过程都去检查变量b的值,在b值为true时,跳出循环并退出run()方法

为了停止正在运行的线程,需要将这个boolean变量的值设置为true,然而因为你是从另一个线程(比如主线程)来设置这个变量的,所以一定要把这个变量标记为volatile,否则正在运行的线程有可能缓存它的值,永远不回主内存查看更新后的值并且无限运行下去。

当我们把变量修饰为volatile,正在运行的线程将不会在其本地堆栈中缓存其值,并始终引用主内存。volatile变量提供了happens before的保证,可用于线程间值同步,A happens before B即A操作的结果,保证B能看到。关于happens before规则我会单独发文讲解。volatile也是理解并掌握Java线程安全最基础的关键字之一,另外一个是synchronize,真的把它们掌握了,你才是真的懂Java线程安全,后面也会单独讲解。

接下来看看Java终止线程的代码示例。在这个例子中可以看到,main/主线程首先启动一个Server线程,然后通过调用stop()方法来停止这个线程,使用了一个boolean的volatile变量来终止正在运行的Server线程。从结果输出中可以清楚地看到,实现了Runnable接口的Server线程运行良好,直到main()方法调用了stop()方法,然后volatile修饰的变量exit的值被设置为true。在下一个迭代中,Server线程检查并发现exit值为true,因此它从循环里和run()方法里跳出来,说明我们的Server线程现在已经终止了。

import java.util.concurrent.TimeUnit;
import static java.lang.Thread.currentThread;

public class Thread2 {

    public static void main(String args[]) throws InterruptedException {
        Server myServer = new Server();
        Thread t1 = new Thread(myServer, "T1");
        //启动子线程Server
        t1.start();
        //停止Server线程
        System.out.println(currentThread().getName() + " is stopping Server thread");
        myServer.stop();
        //等待Server线程终止
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(currentThread().getName() + " is finished now");
    }

}

class Server implements Runnable {

    //创建volatile修饰的boolean变量exit 
    private volatile boolean exit = false;
    
    //迭代检测变量exit,如果是true,就跳出循环
    public void run() {
        while (!exit) {
            System.out.println("Server is running.....");
        }
        System.out.println("Server is stopped....");
    }

    //把变量exit设置成true
    public void stop() {
        exit = true;
    }
    
}

输出结果:

Server is running.....
Server is running.....
Server is running.....
Server is running.....
Server is running.....
Server is running.....
Server is running.....
main is stopping Server thread
Server is stopped....
main is finished now

需要记忆的点

  1. 请确保用于停止线程的boolean变量是volatile修饰的,否则,最坏的情况,你的线程可能不会停止一直运行下去,为什么? 因为,在没有任何同步指令的情况下,例如:volatile修饰符,编译器可以随意缓存boolean变量exit的值,意味着即使主线程将其设置为true,子线程Server也将始终获取到它的值是false的,从而一直运行下去。 这个概念经常在Java面试过程被问到。

  2. 最好在while循环中检查boolean变量。

  3. 最好提供一种停止server的方法,该方法除了更改boolean变量的值外什么也不做。

4)  使用TimeUnit类暂停线程比使用Thread.sleep()方法更好,因为它提高了可读性。你可以预先知道线程是停止2毫秒还是2秒,这在Thread.sleep()中是不可见的。

以上就是关于停止Java线程的内容。确认检查while循环中的停止条件,并确认boolean变量是volatile修饰的。这将确保两件事:

第一,检查这个boolean变量的线程不会将该值缓存在它的本地缓存中。

第二,在设置这个volatile boolean变量之前对一个线程所做的任何更改对其他线程都是可见的。

然而,在实际编程过程中,你可能需要给线程一些时间来完成挂起的任务、释放资源并在停止之前进行相关清理工作。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!