欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

线程暂停和恢复(java线程暂停和恢复)

程序员文章站 2023-11-28 12:27:22
本节讲解的内容是线程的初始化、线程启动、线程中断、suspend()、resume()、stop()和优雅的关闭线程。1.线程初始化和线程的启动首先看下thread的构造方法: public t...
本节讲解的内容是线程的初始化、线程启动、线程中断、suspend()、resume()、stop()和优雅的关闭线程。

1.线程初始化和线程的启动

首先看下thread的构造方法:

    public thread(runnable target, string name) {
        init(null, target, name, 0);
    }

当然thread有多个构造方法,都是调用init方法初始化一个线程。看下init的定义:

 private void init(threadgroup g, runnable target, string name,
                      long stacksize, accesscontrolcontext acc,
                      boolean inheritthreadlocals) {
                      ...
                      }

包含了所属线程组、runable对象、线程名称、线程的栈大小、访问控制权和inheritthreadlocals。当我们通过new关键字创建线程的时候,默认由当前线程来进行空间分配的,而创建出来的线程继承了当前线程的是否为守护线程、线程优先级、contextclassloader以及threadlocal。创建好的线程对象初始化完成后,在堆内存中等待执行。

注意,创建线程时最好执行线程的名字,方便通过命令jstack分析堆栈信息和问题排查。如果不手动指定线程名称,默认是thread-” + nextthreadnum()。

线程启动是调用start()方法,当前线程同步告诉java虚拟机,如果线程规划器空闲,应该立即启动线程。start方法不可以重复调用。

面试中经常会问到直接运行run()方法会怎么?

首先run方法只是个普通方法,直接运行不会启动一个线程,且当前只有一个主线程(当前线程),程序顺序执行,达不到多线程的目的。且run方法可以重复执行。

2.线程的中断

当我们执行一个阻塞io或者长时间没有响应的任务时,我们需要结束线程以避免长时间等待。thread.interrupt(),不是立马中断线程,而是设置一个中断标识位。java中的阻塞函数比如thread.sleep,object.wait,thread.join等,会不断地轮训检测线程中断标识位是否为true,如果为true,会立马抛出interruptedexception,同时把标识位设置为false。在下面的程序中,有两个线程,worker和sleeper,线程中断后,观察下输出的标识位:

public class testinterrupt {
    public static void main(string[] args) {
        thread worker = new thread(()->{
            while (true){}
        }, "work-thread");

        thread sleeper = new thread(()->{
            while (true){
                try {
                    thread.sleep(100);
                } catch (interruptedexception e) {
                    e.printstacktrace();
                }
            }
        }, "sleeper-thread");

        worker.start();
        sleeper.start();
        worker.interrupt();
        sleeper.interrupt();
        system.out.println("work-thread interrupt is " + worker.isinterrupted());
        system.out.println("sleeper-thread interrupt is " + sleeper.isinterrupted());
    }
}
java.lang.interruptedexception: sleep interrupted
	at java.lang.thread.sleep(native method)
	at net.zhifou.concurrent.base.testinterrupt.lambda$main$1(testinterrupt.java:16)
	at java.lang.thread.run(thread.java:748)
work-thread interrupt is true
sleeper-thread interrupt is false

当中断阻塞状态的sleeper线程,立马抛出了interruptedexception异常,重置中断标识位后输出中断标识位为false,worker线程中断标识位仍为true。

thread的两个方法,interrupted和isinterrupted,都是返回中断标识位。我们先看下方法定义:

    public static boolean interrupted() {
        return currentthread().isinterrupted(true);
    }
    
       public boolean isinterrupted() {
        return isinterrupted(false);
    }

然后再看下isinterrupted方法的实现:

    /**
     * tests if some thread has been interrupted.  the interrupted state
     * is reset or not based on the value of clearinterrupted that is
     * passed.
     */
    private native boolean isinterrupted(boolean clearinterrupted);

这个是native方法,看不到实现没关系,看下注释,根据clearinterrupted的值重置或不重置。isinterrupted传入的是false,不清除中断的标识位。而interrupted,传入的是ture,则返回中断标识位后,然后清除中断标识位。

3. suspend()、resume()、stop()

suspend()、resume()、stop()这三个方法,分别对应着,线程暂停、线程恢复、线程停止。观察java源码,都被标识为@deprecated,不建议使用了。

首先我们看suspend()、resume(),它们必须成对出现,且有先后顺序。那么为什么会被废弃呢?

因为线程调用suspend程序暂停后,不会释放任何资源,包括锁,且一直处于睡眠状态,容易引发死锁。而resume因为要和suspend成对出现,所以也没有存在的必要。

那么stop方法为什么不建议使用呢?我们先看源码:

    @deprecated
    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());
    }

最终通过调用stop0(new threaddeath())函数,stop0是native函数,参数是threaddeath,threaddeath是一个异常对象,该对象从native层抛到了java层。线程运行中遇到异常,会导致线程停止,不过不会引起程序退出。

线程是因为异常而停止的,锁会释放,但是不会释放其他的资源,比如打开的socket连接或者io,多不会关闭,引发内存泄漏。

很多时候为了保证数据安全,线程中会编写同步代码,如果当线程正在执行同步代码时,此时调用stop,引起抛出异常,导致线程持有的锁会全部释放,此时就不能确保数据的安全性,出现无法预期的错乱数据,还有可能导致存在需要被释放的资源得不到释放,引发内存泄露。所以用stop停止线程是不推荐的。

4.优雅的终止线程

关闭线程,肯定等到线程释放掉资源,以免造成内存泄漏,所以不建议使用stop方法。那么应该怎么关闭线程呢?答案是两阶段终止模式。

两阶段终止模式,顾名思义,分两个阶段。第一个阶段,向线程发送终止指令,记住这里只是设置一个终止标识位。第二阶段,线程响应指令,然后终止。这里就用到了,thread.interrupt()方法来设置终止标识位。我们先看一张图如下:

线程暂停和恢复(java线程暂停和恢复)

想终止线程,也就是线程是终止状态,如上图所示,线程只能是runnable状态才可以到终止状态。所以线程目前状态可以能是runnable状态,或者是休眠状态。当线程是休眠状态,当我们设置中断标识位时,线程立马会抛出interruptedexception,并且jvm会清除中断标识位,所以在捕获interruptedexception异常后,需要重新设置中断标识位。如下代码:

public class stopthread {

    public static void main(string[] args) throws interruptedexception {
        thread t = new thread(() -> {
            system.out.println("worker-thread start ...");
            while(!thread.currentthread().isinterrupted()) {
                system.out.println("worker-thread working ...");
                try {
                    thread.sleep(1000);
                } catch (interruptedexception e) {
                    e.printstacktrace();
                    thread.currentthread().interrupt();
                }
            }
            system.out.println("worker-thread done ...");
        }, "worker-thread");

        t.start();
        thread.sleep(10000);
        t.interrupt();
    }
}

线程t每次执行while内代码,休眠1000ms,然后继续执行,主线程在休眠10000ms后,线程t设置中断标识位,此时,如果线程t还在sleep中,则抛出interruptedexception异常,jvm清除中断标识位,重新设置中断标识位后,下次执行while代码,条件不符合,线程执行完毕。这样,就完成了两阶段模式。

总结:

  1. 线程启动使用start方法,而线程run方法,不会启动一个线程。
  2. thread.interrupt(),设置中断标识位,具体是否结束线程,线程自己控制。
  3. suspend()、resume()、stop(),这三个方法已经不建议使用,有可能造成死锁和内存泄漏。
  4. 通过两阶段提交模式,来优雅的终止线程。