java如何优雅的停止一个原生Thread线程

最近因为项目中使用到了帧动画。众所周知,帧动画极耗资源,如果一旦涉及到的图片比较多,很容易OOM。

因此为了解决这个问题,采用了自定义SurfaceView,并在里面使用子线程逐帧绘制bitmap的方式来实现。由于这个Surface所要播放的图片list是可以被设置的。也就是说可能上一个动画还没播放完,我就设置了一个新的图片list进来。这样就需要停止掉旧的动画线程,并开启一个新的线程来播放新的动画。于是就涉及到了标题中的这个问题:java如何优雅的停止一个原生Thread线程。

大家都知道Thread类中有一个stop方法可以用来停止线程。那么我们可以直接使用这个方法吗?答案是不可以。因为stop方法已经被官方明确废弃。主要原因就是因为这个方法实在是太简单粗暴了。会造成至少以下几个问题:

(1)stop方法会破坏线程操作的原子性。比如我们对一个线程操作要求是同步的,因为可能在后面有几个很重要的方法必须执行,又或者必须要释放一些资源。这时调用stop方法停止线程,他根本不管什么同不同步,这一套流程有没有完整走完。直接给你停止。

基于以上原因,你现在再去调用stop方法,会直接收到一个UnsupportedOperationException的异常。

既然stop方法不能用。那有没有什么别的办法可以做到呢?有朋友记起Thread类里面有一个interrupt方法,interrupt不就是中断么?那我调用这个方法是不是就可以了。

这里先直接上3点明确的结论吧:

(1)这个方法名起得很具有迷惑性。

(2)这个方法其实本身无法停止一个线程。

(3)有时候我们写的线程因为这个方法停止了,其实并非如此,停止这个线程的根本原因是异常。

接下来上两个例子:

(1)

    private Thread mThread;
    private boolean mIsRunning = true;

    private void startThread(){
        mThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        int i = 0;
                        try {
                            while(mIsRunning) {
                                Log.d(
                                        "MyThreadDemo",
                                        String.valueOf(i)
                                );
                                mThread.sleep(1000);
                                i++;
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            mustDoThings();
                        }
                    }
                }
        );
        mThread.start();
    }

    private void interruptThread(){
        if(mThread!=null){
            mThread.interrupt();
        }
    }

    private void mustDoThings(){
        Log.d("MyThreadDemo","必须要执行的代码");
    }

我们先调用startThread方法开启一个线程,待线程跑一会。之后再调用interruptThread方法。看一下Log日志:

06-27 17:56:18.762 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 4
06-27 17:56:19.763 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 5
06-27 17:56:20.764 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 6
06-27 17:56:21.764 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 7
06-27 17:56:22.765 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 8
06-27 17:56:23.766 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 9
06-27 17:56:24.766 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 10
06-27 17:56:25.433 2480-2935/com.wm.mythreaddemo W/System.err: java.lang.InterruptedException
        at java.lang.Thread.sleep(Native Method)
06-27 17:56:25.434 2480-2935/com.wm.mythreaddemo W/System.err:     at java.lang.Thread.sleep(Thread.java:1031)
        at java.lang.Thread.sleep(Thread.java:985)
        at com.wm.mythreaddemo.MainActivity$1.run(MainActivity.java:74)
        at java.lang.Thread.run(Thread.java:818)
06-27 17:56:25.434 2480-2935/com.wm.mythreaddemo D/MyThreadDemo: 必须要执行的代码

你看线程这不是停止了么?而且finally里面必须调用的代码mustDoThings方法也成功调用了。但再仔细看看就可以发现,在finally之前,有一个InterruptedException被抛出。所以,这次线程的停止和mustDoThings方法的调用完全是因为这个异常。

    
    private Thread mThread;
    private boolean mIsRunning = true;

    private void startThread(){
        mThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        int i = 0;
                        while(mIsRunning) {
                            Log.d(
                                    "MyThreadDemo",
                                    String.valueOf(i)
                            );
                            i++;
                        }
                    }
                }
        );
        mThread.start();
    }

    private void interruptThread(){
        if(mThread!=null){
            mThread.interrupt();
        }
    }

我们还是先startThread,再interruptThread。会发现,这次怎么调用interruptThread方法你都停不了这个线程。

其实interrupt方法只是设置了一个停止线程的标识位。如果线程处于阻塞状态,调用interrupt方法会抛出InterruptException异常,并且清除停止线程的标识位。

既然知道了stop不能用,interrupt方法又不能直接停止一个线程。停止线程的正确姿势是什么?这里提供比较常用的两种:

(1)自己维护停止线程的标识位

    private Thread mThread;
    private boolean mIsRunning = true;

    private void startThread(){
        mThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        int i = 0;
                        while(mIsRunning){
                            Log.d(
                                    "MyThreadDemo",
                                    String.valueOf(i)
                            );
                            try {
                                mThread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            i++;
                        }
                        mustDoThings();
                    }
                }
        );
        mThread.start();
    }

    private void stopBoolThread(){
        mIsRunning = false;
    }

    private void mustDoThings(){
        Log.d("MyThreadDemo","必须要执行的代码");
    }
这就是完全自己维护一个标识位来控制while循环是否需要继续执行。如果不再继续,在while之后进行必要操作的处理。

(2)利用interrupt方法设置的标识位

既然Thread提供了一个设置标识位,那么我们可以利用这个方法来实现。Thread还提供了一个isInterrupted方法来获取这个标识位。

    private Thread mThread;
    private void startThread(){
        mThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        int i = 0;
                        try {
                            while(!mThread.isInterrupted()){
                                Log.d(
                                        "MyThreadDemo",
                                        String.valueOf(i)
                                );
                                mThread.sleep(1000);
                                i++;
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            mustDoThings();
                        }
                    }
                }
        );
        mThread.start();
    }

    private void interruptThread(){
        if(mThread!=null){
            mThread.interrupt();
        }
    }

一定要注意一下try-catch的包裹范围问题。因为就像之前所说的,如果你对一个阻塞的线程调用interrupt方法,他会清除停止线程的标识位,即会让这个标识位变成false。如果你写了以下代码,通过interrupt方法你是停止不了这个线程的。

一个错误的演示:

private void startThread(){
        mThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        int i = 0;
                        while(!mThread.isInterrupted()){
                            Log.d(
                                    "MyThreadDemo",
                                    String.valueOf(i)
                            );
                            try {
                                mThread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            i++;
                        }
                        mustDoThings();
                    }
                }
        );
        mThread.start();
}

你可能感兴趣的:(技术探究)