Java并发编程之停止线程

1. 线程在什么情况下会停止?

在三种情况下会导致线程停止:

  • run方法正常执行完毕
  • 执行过程中出现异常
  • 其它线程干预当前线程,如interrupt、volatile修饰的标识符、已经被弃用的stop、suspend等方法

2. 停止线程的正确方法:interrupt

使用interrupt来通知需要被停止的线程以及该线程如何配合,而不是强制停止,因为被停止的线程对本身的业务逻辑是最熟悉的,而发出停止信号的线程对其业务逻辑是不了解的,我们只能告诉被停止的线程该中断了,而被中断的线程本身拥有最终决定权,这样可以保障数据安全。

  1. 在非阻塞状态下停止线程
public class MyThreadNormal implements Runnable {
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(count++);
        }
        System.out.println("线程被停止");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadNormal());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
.......
308827
308828
308829
线程被停止
  1. 在阻塞状态下停止线程
public class MyThreadSleep implements Runnable {
    @Override
    public void run() {
        System.out.println("开始阻塞");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束阻塞");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
开始阻塞
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at core.stop.right.MyThreadSleep.run(MyThreadSleep.java:8)
	at java.lang.Thread.run(Thread.java:748)
结束阻塞
public class MyThreadWait implements Runnable {
    private Object obj = new Object();
    @Override
    public void run() {
        System.out.println("开始阻塞");
        synchronized (obj) {
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("结束阻塞");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadWait());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
开始阻塞
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at core.stop.right.MyThreadWait.run(MyThreadWait.java:10)
	at java.lang.Thread.run(Thread.java:748)
结束阻塞

wait方法和sleep方法都会导致阻塞状态,在阻塞状态下如果收到中断信号,便会响应中断,抛出异常。所以,如果代码中存在wait或sleep方法就没必要去写!Thread.currentThread().isInterrupted()去检测中断信号,因为前者已经能响应中断。

3. 中断线程的最佳实践

在while中try/catch中存在以下问题:

public class MyThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(count++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThread());
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}
0
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at core.stop.best.MyThread.run(MyThread.java:10)
	at java.lang.Thread.run(Thread.java:748)
1
2

如上述代码所示,在while循环内try/catch时,主线程发出中断请求,子线程响应了中断并捕获异常,但是程序依然继续执行,那是因为一旦sleep方法响应中断,响应后就会把中断状态清除,所以再进行检查中断信号时是检查不到的。
针对while内try/catch存在的问题,最佳解决方案是如果run方法中调用了其他方法且方法中需要抛出异常,那么久在被调用方法签名上抛出异常,并在catch中恢复中断,代码如下:

public class MyThreadOne implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("进入try");
                throwMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
        }
    }

    private void throwMethod() throws InterruptedException {
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadOne());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
进入try
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at core.stop.best.MyThreadOne.throwMethod(MyThreadOne.java:18)
	at core.stop.best.MyThreadOne.run(MyThreadOne.java:9)
	at java.lang.Thread.run(Thread.java:748)

上述代码的run方法中调用了throwMethod方法,throwMethod方法中调用了sleep方法并在方法签名上抛出异常,这样做的好处在于:

  • 被调用方在方法签名上抛出异常,调用方必须捕获异常,这样调用方就可以处理这个异常,进而在catch中处理,比如重新标记中断状态,进而停止线程;
  • 如果直接在被调用方处理异常,那么调用方就不知道是否有异常,就无法定位到异常发生的位置。

4. 停止线程的错误方法:stop、suspend和volatile boolean标记位

  1. stop方法会导致线程运行一半突然停止,没法完成一个基本单位的操作,造成脏数据
  2. suspend方法不会释放锁,如果没有被唤醒,容易造成死锁
  3. volatile boolean标记位
    1. 在非阻塞状态下停止线程
public class MyThreadVolatile implements Runnable {

    private static volatile boolean canceled = false;

    @Override
    public void run() {
        int count = 0;
        while (!canceled) {
            System.out.println(count++);
        }
        System.out.println("线程结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadVolatile());
        thread.start();
        Thread.sleep(500);
        MyThreadVolatile.canceled = true;
    }
}
  1. 在阻塞状态下停止线程
public class MyThreadVolatileSleep implements Runnable {

    private static volatile boolean canceled = false;

    @Override
    public void run() {
        while (!canceled) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadVolatileSleep());
        thread.start();
        Thread.sleep(500);
        MyThreadVolatileSleep.canceled = true;
    }
}

因为sleep方法能响应interrupt方法中断线程,而不能响应volatile修饰的canceled关键字,所以在阻塞状态下,线程是无法中断的。

5. 停止线程相关重要函数解析

Thread类中判断是否已被中断的方法如下:

  • interrupted方法,是Thread类的静态方法,判断完线程状态后会清除中断状态
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
  • isInterrupted,不会清除中断状态
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
  • Thread.interrupted()是个静态方法,该方法属于Thread类,而不属于子线程对象,所以该方法的目标对象是当前线程,即执行该方法的线程,而不管本方法由哪个对象调用的,比如:主线程中用子线程对象调用interrupted()方法,判断的是主线程,而不是子线程,代码如下:
public class MyThreadInterrupted implements Runnable {
    @Override
    public void run() {
        for (;;) {

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThreadInterrupted());
        thread.start();
        thread.interrupt();
        System.out.println(thread.isInterrupted());//子线程true
        System.out.println(thread.interrupted());//主线程false
        System.out.println(Thread.interrupted());//主线程false
        thread.join();
        System.out.println("主线程结束");
    }
}
true
false
false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值