大家好!我是你的安卓开发陪跑员。今天咱们聊一个看似简单、实则坑多到能练跨栏的技术点——中断线程。
你肯定遇到过这种场景:App里开个后台线程下载文件,用户突然点返回键溜了,结果线程还在那儿吭哧吭哧下载,白白浪费电量和流量。这就像请朋友来家里聚餐,人都走光了,他还在厨房疯狂炒菜,你说尴不尴尬?
更糟的是,有些线程直接“赖”在后台不走了,变成“钉子户”,导致内存泄漏、App卡顿,最后被系统强制“拆迁”(崩溃)——这谁顶得住啊!
所以今天,咱们就彻底搞懂怎么礼貌地对线程说:“别干了,收工吧!”
一、中断线程:不是强制击杀,是温柔提醒
很多新手以为中断线程就是强行把线程“干掉”,像终结者一样霸气。但实际上,Java设计的线程中断机制更像是对线程说:“那个……麻烦你自觉点停下来好吗?”
为什么这么设计?因为线程可能在执行关键任务(比如写数据库),强行终止可能导致数据损坏。所以中断机制只是设置一个标志位,具体要不要停、何时停,还得线程自己决定。
这里必须点名批评两个“伪中断”方法(千万别用!):
- stop():简单粗暴,直接让线程原地去世,但可能导致数据混乱或锁没释放,早在JDK 1.2就过时了。
- suspend() 和 resume():让线程暂停和继续,但容易引发死锁,好比把朋友晾在门口不让进也不让走。
正确的姿势只有一个:interrupt()。
二、interrupt() 怎么用?从青铜到王者
青铜选手:直接调interrupt()(然后翻车)
Thread worker = new Thread(() -> {
while (true) {
System.out.println("我在摸鱼……啊不,在工作!");
}
});
worker.start();
Thread.sleep(1000);
worker.interrupt(); // 然并卵,线程继续摸鱼
看到没?光调用interrupt(),线程根本不理你,因为没人检查中断状态。
白银选手:检查isInterrupted()
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("努力工作中!");
}
System.out.println("收到,这就下班!");
});
worker.start();
Thread.sleep(10);
worker.interrupt(); // 成功中断!
这里我们在循环里加了!Thread.currentThread().isInterrupted()判断,线程会定期检查“老板是不是喊停了”。一旦发现中断标志,立马退出循环。
但问题来了——如果线程在睡觉(比如调用了sleep())怎么办?
黄金选手:处理InterruptedException
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("工作10秒……");
Thread.sleep(10000); // 模拟耗时任务
} catch (InterruptedException e) {
System.out.println("谁吵我睡觉?!等等,是老板喊停?");
Thread.currentThread().interrupt(); // 重新设置中断标志
}
}
System.out.println("溜了溜了");
});
注意这里的关键操作:
- 当线程在sleep()时被中断,会抛出InterruptedException。
- 捕获异常后,必须再次调用interrupt()设置标志,因为捕获异常时中断状态会被清除。
- 这样外层的循环判断才能检测到中断。
忘记重新中断是最常见的坑!很多人在catch块里直接return,虽然也行,但如果后面还有清理代码就走不到了。
三、实战:安卓后台下载任务中断
来看一个真实场景:在Activity中启动下载线程,当Activity销毁时中断线程。
完整示例(Kotlin + AndroidX):
class DownloadActivity : AppCompatActivity() {
private var downloadThread: Thread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_download)
findViewById<Button>(R.id.btn_start).setOnClickListener {
startDownload()
}
findViewById<Button>(R.id.btn_cancel).setOnClickListener {
cancelDownload()
}
}
private fun startDownload() {
downloadThread = Thread {
try {
for (i in 1..100) {
// 检查中断
if (Thread.currentThread().isInterrupted) {
Log.d("Download", "下载被取消")
return@Thread
}
// 模拟下载进度
Log.d("Download", "下载进度:$i%")
Thread.sleep(100)
}
Log.d("Download", "下载完成!")
} catch (e: InterruptedException) {
Log.d("Download", "下载被中断")
Thread.currentThread().interrupt() // 保持中断状态
}
}.apply { start() }
}
private fun cancelDownload() {
downloadThread?.interrupt()
downloadThread = null
}
override fun onDestroy() {
cancelDownload() // 防止Activity泄漏
super.onDestroy()
}
}
这个例子里有几个重点:
- 在循环内定期检查isInterrupted
- 正确处理InterruptedException并恢复中断状态
- 在Activity销毁时主动中断线程,避免内存泄漏
- 中断后设置thread为null,防止重复中断
四、进阶技巧:用标志位实现更灵活的控制
有时候interrupt()不够用,比如你想让线程完成当前任务再停止,而不是立即停止。这时可以用自定义标志位:
public class StoppableTask implements Runnable {
private volatile boolean stopped = false; // volatile确保可见性
public void stop() {
stopped = true;
}
@Override
public void run() {
while (!stopped) {
// 执行任务...
System.out.println("Working...");
}
System.out.println("Stopped gracefully");
}
}
用volatile确保一个线程修改stopped后,其他线程立即可见。这种方式比interrupt()更温和,适合需要“干完手里这点活再走”的场景。
五、现代方案:Kotlin协程才是版本答案
说实话,在2023年还手动管理Thread有点过时了。Kotlin协程提供了更优雅的中断机制:
class CoroutineDownloadActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.IO + Job())
private fun startDownload() {
scope.launch {
try {
for (i in 1..100) {
ensureActive() // 检查协程是否活跃
withContext(Dispatchers.Main) {
// 更新UI显示进度
}
delay(100) // 非阻塞延迟
}
} catch (e: CancellationException) {
Log.d("Download", "协程被取消")
}
}
}
private fun cancelDownload() {
scope.cancel() // 一键取消所有子协程
}
}
协程的优势很明显:
ensureActive()和delay()自动响应取消- 结构化并发,取消一个协程会自动取消所有子协程
- 不用手动检查中断状态,少写很多模板代码
六、总结
好了,我们来划下重点:
- 中断本质是协作机制:靠线程自觉,不是强制击杀
- 核心三件套:interrupt()、isInterrupted()、InterruptedException
- 黄金法则:捕获InterruptedException后记得重新中断
- 安卓特色:在onDestroy()中中断线程,避免内存泄漏
- 现代开发:能用协程就别用原生线程
记住,优雅地中断线程,就像礼貌地结束对话——给对方留足面子,自己也省心。下次遇到“钉子户”线程,你知道该怎么做了吧?
(全文完)

被折叠的 条评论
为什么被折叠?



