线程的中断与暴力停止

线程的中断

stop()方法的线程不安全性

Thread.stop()方法可以停止一个运行中的线程,但是这个方法已经被弃用了,这是为什么呢?

先看下面一段代码:

package com.morris.concurrent.thread.interrupt;

import java.util.concurrent.TimeUnit;

/**
 * 演示stop()方法带来的线程安全问题
 */
public class StopDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        Object lock = new Object();

        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                synchronized (lock) {
    
    
                    System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("thread->" + Thread.currentThread().getName() + " 等待3s");
                    System.out.println("thread->" + Thread.currentThread().getName() + " release lock.");

                }
            } catch (Throwable e) {
    
     // 抛出ThreadDeath Error
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
    
    
            synchronized (lock) {
    
    
                System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
            }
        });

        t1.start();

        // 休眠100ms,等待t1线程运行
        TimeUnit.MILLISECONDS.sleep(100);

        t2.start();

        t1.stop();
        
    }
}

运行结果如下:

thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
java.lang.ThreadDeath
	at java.lang.Thread.stop(Thread.java:853)
	at com.morris.concurrent.thread.interrupt.StopDemo.main(StopDemo.java:41)

从运行结果中我们可以看到,调用了t1.stop()方法后,可以发现,t1线程抛出了ThreadDeath Error并且t1线程释放了它所占有的锁。

一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

这个也是不提倡使用kill -9命令强制杀死线程的原因,这样可能会导致一些难以处理的线程安全问题。

interrupt()中断线程

stop()方法除了会产生线程安全问题外,而且对被终止线程是一种强制式的终止。而在java中,线程之间更多的是提倡一种协作式。

每一个线程都有一个boolean类型标志,用来表明当前线程是否被中断。interrupt()方法是用来设置线程的中断标志位为true,被中断的线程可以自行决定是否对这个中断进行处理(直接终止或者进行数据保护等处理后在终止或者不终止)。

我们可以通过调用Thread.currentThread().isInterrupted()或者Thread.interrupted()来检测线程的中断标志是否被置位。

这两个方法的区别是:

  • Thread.currentThread().isInterrupted():对象方法,不清除中断标志位。
  • Thread.interrupted():静态方法,清除中断标志位。

所以说调用线程的interrupt()方法不会中断一个正在运行的线程,这个机制只是设置了一个线程中断标志位,如果在程序中你不检测线程中断标志位,那么即使设置了中断标志位为true,线程也一样照常运行。

一般来说中断线程分为三种情况:

  1. 中断非阻塞线程
  2. 中断阻塞线程
  3. 中断不可中断线程

中断非阻塞线程

采用线程共享变量

这种方式比较简单可行,需要注意的一点是共享变量必须设置为volatile,这样才能保证修改后其他线程立即可见。

package com.morris.concurrent.thread.interrupt;

/**
 * 中断非阻塞的线程:使用共享变量
 */
public class InterruptNonBlockWithShareVariable extends Thread {
    
    

    // 设置线程共享变量
    private volatile boolean isStop = false;

    @Override
    public void run() {
    
    
        while(!isStop) {
    
    
            long beginTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + " is running");
            // 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
            while (System.currentTimeMillis() - beginTime < 1000) {
    
    }
        }
        if (isStop) {
    
    
            System.out.println(Thread.currentThread().getName() + " is interrupted");
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        InterruptNonBlockWithShareVariable thread = new InterruptNonBlockWithShareVariable();
        thread.start();

        Thread.sleep(5000);
        thread.isStop = true;

    }
}

采用中断机制

package com.morris.concurrent.thread.interrupt;

/**
 * 中断非阻塞的线程:使用中断机制
 */
public class InterruptNonBlockWithInterrupt extends Thread {
    
    

    @Override
    public void run() {
    
    
        while(!Thread.currentThread().isInterrupted()) {
    
    
            long beginTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + " is running");
            // 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
            while (System.currentTimeMillis() - beginTime < 1000) {
    
    }
        }

         System.out.println(Thread.currentThread().getName() + " interrupted flag:" + Thread.currentThread().isInterrupted());
         System.out.println(Thread.currentThread().getName() + " is interrupted");
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        InterruptNonBlockWithInterrupt thread = new InterruptNonBlockWithInterrupt();
        thread.start();

        Thread.sleep(5000);
        thread.interrupt();

    }
}

中断阻塞线程

当线程调用Thread.sleep()、Thread.join()、Object.wait()、LockSupport.park()或者调用阻塞的i/o操作方法时,都会使得当前线程进入阻塞状态。

如果在线程处于阻塞状态时,调用interrupt()方法设置线程中断标志位时会抛出一个InterruptedException异常,并且会清除线程中断标志位(设置为false),这样一来线程就能退出阻塞状态。

如果线程已经被中断了,然后再调用阻塞方法,那么会直接抛出异常。

采用共享变量

package com.morris.concurrent.thread.interrupt;

/**
 * 中断阻塞的线程:使用共享变量
 * 缺点:使用共享变量无法及时中断阻塞线程,因为线程不能第一时间接到中断通知,需等线程被唤醒时才知道。
 */
public class InterruptBlockThreadWithVariable extends Thread {
    
    

    // 设置线程共享变量
    private volatile boolean isStop = false;

    @Override
    public void run() {
    
    
        while(!isStop) {
    
    
            System.out.println(Thread.currentThread().getName() + " is running");
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
            }
        }
        System.out.println(Thread.currentThread().getName() + " is interrupted");
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        InterruptBlockThreadWithVariable thread = new InterruptBlockThreadWithVariable();
        thread.start();

        Thread.sleep(1000);
        thread.isStop = true;

    }
}

缺点:使用共享变量无法及时中断阻塞线程,因为线程不能第一时间接到中断通知,需等线程被唤醒时才知道。

采用中断机制

package com.morris.concurrent.thread.interrupt;

/**
 * 中断阻塞的线程:使用中断机制
 * 注意抛出异常会清除中断标志位
 */
public class InterruptBlockThreadWithInterrupt extends Thread {
    
    

    @Override
    public void run() {
    
    
        // 这里调用的是非清除中断标志位的isInterrupted方法
        while(!Thread.currentThread().interrupted()) {
    
    
            System.out.println(Thread.currentThread().getName() + " is running");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                // 由于调用sleep()方法清除状态标志位 所以这里需要再次重置中断标志位 否则线程会继续运行下去
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + " is interrupted");
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        InterruptBlockThreadWithInterrupt thread = new InterruptBlockThreadWithInterrupt();
        thread.start();

        Thread.sleep(5000);
        thread.interrupt();

    }
}

暴力停止不可中断线程

package com.morris.concurrent.thread.interrupt;

import java.util.concurrent.TimeUnit;

/**
 * 暴力停止不可中断的线程,如正在获取synchronized锁的线程
 */
public class ViolenceStopNotInterruptThread {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        ThreadService threadService = new ThreadService();
        threadService.execute(() -> {
    
    
            while (true) {
    
    
                System.out.println("I am working...");
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

        });

        // 等待threadService里面的线程充分运行
        TimeUnit.MILLISECONDS.sleep(1000);

        threadService.shutdown(1_000);

    }

    private static class ThreadService {
    
    

        // 这个线程负责启动一个守护线程,当这个线程结束时,它创建的线程也会随之结束
        private Thread executeThread;

        // 用于标记任务是否结束
        private boolean isStop = false;

        /**
         * 执行任务
         * @param task
         */
        public void execute(Runnable task) {
    
    
            executeThread = new Thread(() -> {
    
    
                // t是真正执行任务的线程
                Thread t = new Thread(task);
                t.setDaemon(true);
                t.start();
                try {
    
    
                    // 这里要join,不然守护线程还没开始运行就可能结束了,另外也便于超时结束
                    executeThread.join();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                isStop = true;
            });

            executeThread.start();
        }

        /**
         * 等待mills还未结束就终止线程
         * @param mills
         */
       public void shutdown(long mills) {
    
    

           long endMills = System.currentTimeMillis() + mills;

           long remainMills = mills;

           while (remainMills > 0 && !isStop) {
    
    
               try {
    
    
                   TimeUnit.MILLISECONDS.sleep(10);
               } catch (InterruptedException e) {
    
    
                   e.printStackTrace();
               }
               remainMills = endMills - System.currentTimeMillis();
           }
           // 中断线程,executeThread结束后,其创建的守护线程也会随之结束
           executeThread.interrupt();
       }

    }
}

猜你喜欢

转载自blog.csdn.net/u022812849/article/details/108709330