您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

android 线程中断

时间:02-05来源:作者:点击数:

项目代码中使用了线程的中断函数,和判断线程是否中断了停止while循环,项目使用一直也什么问题,突然间,我就想证实一下,这样是否有问题,代码如下:

    class MyThread : Thread() {
        override fun run() {
            Log.i("ABCD", "11111")
            try {
                sleep(10_000)
            } catch (e: Exception) {
                Log.i("ABCD", "发生异常", e)
            }

            Log.i("ABCD", "22222")
        }
    }

    private var thread: MyThread? = null

    fun startThread(view: View) {
        if (thread == null) {
            thread = MyThread().apply { start() }
        }
    }

    fun interruptThread(view: View) {
        thread?.interrupt()
        thread {
            while (thread?.isInterrupted == false) {
                Log.i("ABCD", "线程还没被中断")
                Thread.sleep(1000)
            }
            Log.i("ABCD", "线程已被中断")
            thread = null
        }
    }

startThread和interruptThread分别对应两个按钮的点击事件,当我点击开启线程按钮时线程开始启动,然后我立马按下中断线程按钮,输出的Log如下:

2020-09-25 10:43:04.978 14718-15131/com.evendai.demo I/ABCD: 11111
2020-09-25 10:43:07.187 14718-15131/com.evendai.demo I/ABCD: 发生异常
    java.lang.InterruptedException
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:443)
        at java.lang.Thread.sleep(Thread.java:359)
        at com.evendai.demo.MainActivity$MyThread.run(MainActivity.kt:41)
2020-09-25 10:43:07.187 14718-15131/com.evendai.demo I/ABCD: 22222
2020-09-25 10:43:07.187 14718-15132/com.evendai.demo I/ABCD: 线程还没被中断
2020-09-25 10:43:08.188 14718-15132/com.evendai.demo I/ABCD: 线程还没被中断
2020-09-25 10:43:09.189 14718-15132/com.evendai.demo I/ABCD: 线程还没被中断

可以看到,在调用线程中断函数时,sleep()函数会立马抛出异常,我们进行了try catch,这时我们看到后面的“2222"立马就输出了,然后另一个线程检测此线程的中断状态时,一直是未中断的状态,所以,实验证明,即使调用thread.interrupt()函数,线程run方法里的代码可能还是在执行的,interupt()函数无法阻断run函数中代码的执行。

实验到这里,心里一惊,项目中一直是这么用的,好像也没什么问题,可能出了问题我也不知道,比如有时候卡死了也不知道是这不是这个导致的,我项目中run方法里面写了一个while循环,通过线程的isInterrupted状态来决定何时退出循环,一直没出问题可能是因为我把整个while做了一个try catch,当调用interupt()时,run里面抛出的中断异常被捕捉了,所以没什么问题,但现在,起码我知道了,可能run里面的一些代码在调用了interupt()后还是在执行的。

OK,到了这里总结一下,一般我们在线程写while循环时,条件要使用一个自定义的boolean变量来控制,当不需要循环时,把该变量设置为false。不要使用interrupt()函数和isInterrupted状态来控制while循环。赶紧把项目的实现改一改。

后续:代码还是不能乱改,要搞搞清楚,我项目中while循环里面会从队列里面读数据,简化的代码如下:

while (needRun) {
	val byteArray = mPCMDataQueue.take()
}

take()函数的功能就是等待队列里的数据,如果没有数据就一直等着,这会阻塞线程的,所以如果我在想退出循环的地方,只是简单的把needRun设置为false,这是有问题的,因为代码走不到while(needRun)这里了,代码一直在mPCMDataQueue.take()这里等着数据的输入的呢?所以还是得调用thread.interrupt(),它会让take()函数抛出异常,这样我们的代码才能继续往下走。所以interrupt()函数还是有用的,同时while也是需要使用自己的boolean变量来控制,比如,因为上面我们试验也知道了,调用interrupt()函数后isInterrupted状态还可能是false的,示例代码如下:

    class MyThread : Thread() {
        private var queue: BlockingQueue<ByteArray> = LinkedBlockingQueue(5)
        override fun run() {
            while (!isInterrupted) {
                Log.i("ABCD", "11111")
                try {
                    val data = queue.take()
                } catch (e: Exception) {
                    Log.i("ABCD", "发生异常", e)
                }

                Log.i("ABCD", "22222")
            }
        }
    }

当代码执行到queue.take()处时,我们调用interrupt()函数,此时会抛出异常,由于我们做了try catch,此时cpu会从catch的地方继承往下执行,然后也会执行到while(!isInterrupted)的地方,此时isInterrupted为false,所以循环继续,这样我们的while循环就永远也退不出去了。

换个写法:

    class MyThread : Thread() {
        private var queue: BlockingQueue<ByteArray> = LinkedBlockingQueue(5)
        override fun run() {
            try {
                while (!isInterrupted) {
                    Log.i("ABCD", "11111")
                    val data = queue.take()
                    Log.i("ABCD", "22222")
                }
            } catch (e: Exception) {
                Log.i("ABCD", "发生异常", e)
            }
        }
    }

这次的区别在于,我们是把整个while给做了try catch,所以发生异常时,CPU直接就跳到catch的地方执行了,这样就跳出了while循环了,我项目最开始就是这么写的,所以好像也没什么问题。但是也还是不推荐使用isInterrupted做为while循环的控制条件的,我假设你while中调用了别人的函数,比如使用OkHttp框架请求网络,假设调用interrupt()函数时抛出的中断异常是在OkHttp的函数里面,而且别人做了try catch,则我们在while循环外面写的try catch就无法捕捉到这个异常了,那while循环就没法退出了,所以控制while循环还是需要使用自定义变量。所以,最佳做法应该是interrupt()函数和自定义变量一起用,如下:

class MyThread : Thread() {

	private var needRun = true
    private var queue: BlockingQueue<ByteArray> = LinkedBlockingQueue(5)    
    private var frameLossCount: Int = 0

    fun addDatas(datas: Triple<ByteArray, Int, Int>) {
        val offer = queue.offer(datas) // 如果队列满了,则放不进去且返回false
        if (!offer) {
            Logger.e("MyThread", "丢帧:${++frameLossCount}帧")
        }
    }
    
    override fun run() {
        try {
            while (!isInterrupted && needRun) {
                val data = queue.take() // 如果队列中没有数据,则一直阻塞,直到有数据
                // TODO 做耗时操作
            }
        } catch (e: Exception) {
            Log.i("ABCD", "发生异常", e)
        }
    }
	
	fun close() {
		needRun = flase
		interrupt()
	}
}

这种模式在音视频编解码时特别有用,当我们得到一些音视频数据时,只需要调用MyThread的addDatas方法来往队列中加入音视频数据,然后子线程中的run函数会取出列表中的数据,然后就可以做耗时的编解码的事情了,示例如下:

val myThread = Thread() // 创建线程对象
myThread.start()		// 启动线程

myThread.addDatas(datas)// 往队列中放入数据

myThread.close()		// 关闭线程
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门