一、守护线程

官方API解释

The Java Virtual Machine exits when the only threads running are all daemon threads.

  • 当主线程死亡后,守护线程会跟着死亡
  • 可以帮助做一些辅助性的东西,如“心跳检测”
  • 设置守护线程:public final void setDaemon(boolean on)

This method must be invoked before the thread is started.

  • 设置该线程为守护线程必须在启动它之前

案例演示

public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            Thread innerThread = new Thread(() -> {
                while (true){
                    System.out.println("我在进行心跳检测....");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            innerThread.setDaemon(true);//设置为t线程的守护线程

            innerThread.start();
            try {
                Thread.sleep(1000);//休眠1秒后,模仿主线程正在执行操作
                System.out.println("线程已经完成执行");//子线程退出
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();
    }
}

结果

我在进行心跳检测…
我在进行心跳检测…
我在进行心跳检测…
我在进行心跳检测…
线程已经完成执行

二、join方法

join()方法

  • Waits for this thread to die.
  • 默认传入的数字为0,就是父线程会等待执行join方法的子线程一直到死去
  • join必须在start方法之后,其是对wait方法的封装
public class ThreadJoin {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{

            for (int i = 0; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName()+"->" + i);
            }

        });
        Thread t2 = new Thread(()->{

            for (int i = 0; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName()+"->" + i);
            }

        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"->" + i);
        }
    }
}
  • 对于这个例子,main线程会等待两个子线程t1、t2执行结束后,再执行,但是t1和t2两个线程之间是交互执行的

join(long millis,int nanos)方法

  • 跟原来不传入参数的区别在于:不会一直等到子线程死去,而是等待一个设置的时间

三、interrupt()

官方API解释

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

  • 意思:当该线程在wait()、join()、sleep(long, int)状态时,如果被打断,则会收到一个异常提醒
  • 但是只要线程被打断,都可以通过isInterrupted()方法检测到打断的状态,观察下面源码,可以找到interrupt0()方***去修改线程的状态
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // 修改线程的是否打断的状态
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

观察下面两个判断打断状态的方法

  • public static boolean interrupted()
  • public boolean isInterrupted()

为什么要这么设置呢?
原因在于

  • interrupted()是一个静态方法,可以在Runnable接口实例中使用
  • isInterrupted()是一个Thread的实例方法,在覆写Thread的run方法时使用
public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.interrupted());
        });

        Thread t2 = new Thread(){
            @Override
            public void run() {
                System.out.println(isInterrupted());
            }
        };

    }
}

四、如何关闭线程

优雅的方法

1. 通过一个布尔值控制

public class ThreadCloseGraceful {

    private static class Worker extends Thread{
        private volatile boolean start = true;

        @Override
        public void run() {
            while (start){
				//执行工作
            }
        }
        public void shutdown(){//关闭线程
            this.start = false;
        }
    }

    public static void main(String[] args) {
        Worker worker = new Worker();
        worker.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        worker.shutdown();
    }
}

2.判断是否打断

@Override
public void run() {
    while (!Thread.interrupted()){
		//程序逻辑
    }
}

强制的方法

  • 该方式适用于:当线程在执行一个很耗时的操作卡住时,此时可以设置超时强制关闭的功能
  • 基本思路:设置一个执行线程和一个守护线程,守护线程真正的执行代码逻辑,而执行线程阻塞。当执行线程被打断时,执行线程退出阻塞执行完毕,此时守护线程也跟随着关闭
public class ThreadService {

    private Thread executeThread ;//执行线程

    private boolean finished = false;

    public void execute(Runnable task){//异步的执行
        executeThread = new Thread(() -> {
            Thread runner = new Thread(task);
            runner.setDaemon(true);//设置为守护线程,通过该方式关闭
            runner.start();
            try {
                runner.join();//执行线程会被阻塞住
                finished = true;//判断子线程是否执行完成
            } catch (InterruptedException e) {

            }
        });
        executeThread.start();
    }


    public void shutdown(long mills){//关闭方法
        long currentTime  = System.currentTimeMillis();

        while (!finished){//任务完成后还没有超时,则不用继续判断
            if (System.currentTimeMillis()-currentTime>=mills){//超时
                System.out.println("任务超时!!!");
                executeThread.interrupt();//关闭执行线程
                break;
            }
            try {
                executeThread.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("执行线程被打断");
                break;
            }
        }
        finished = false;
    }
}

测试强制关闭

public class ThreadCloseForce {

    public static void main(String[] args) {
        ThreadService service = new ThreadService();
        long start = System.currentTimeMillis();
        service.execute(()->{
            //模拟加载一个大型的资源
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        service.shutdown(1000);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}
  • 使用场景:分布式文件拷贝,如果拷贝的时间过长,则关闭该程序,防止程序一直阻塞