Andorid 多线程(二):Thread 终止的方法
Android 线程终止的方法
前人:屌丝迷途 https://www.cnblogs.com/l2rf/p/5566895.html
Marker_Sky https://www.jianshu.com/p/49349eee9abc
1 | Thread.currentThread().getName()<br>Thread.currentThread().getId() |
先来看下问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | package com.gatsby.threadsafe; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button btn1; private String TAG = "gatsby" ; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main);<br> btn1 = (Button) findViewById(R.id.btn1); btn1.setOnClickListener( this ); Thread thread = new Thread( new crushRunable()); thread.start(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn1: break ; } } class crushRunable implements Runnable { int i = 1 ; @Override public void run() { while (i < 100 ) { Log.d( "gatsby" , "Thread.currentThread().getId()->" + Thread.currentThread().getId() + " i->" + (i++)); try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 当活动即将可见时调用 */ @Override protected void onStart() { super .onStart(); Log.d(TAG, "The onStart() event" ); } /** * 当活动可见时调用 */ @Override protected void onResume() { super .onResume(); Log.d(TAG, "The onResume() event" ); } /** * 当其他活动获得焦点时调用 */ @Override protected void onPause() { super .onPause(); Log.d(TAG, "The onPause() event" ); } /** * 当活动不再可见时调用 */ @Override protected void onStop() { super .onStop(); Log.d(TAG, "The onStop() event" ); } /** * 当活动将被销毁时调用 */ @Override public void onDestroy() { super .onDestroy(); Log.d(TAG, "The onDestroy() event" ); } } |
线程ID 多了一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 08 - 06 12 : 02 : 30.725 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 1 08 - 06 12 : 02 : 30.727 28085 28085 D gatsby : The onStart() event 08 - 06 12 : 02 : 30.729 28085 28085 D gatsby : The onResume() event 08 - 06 12 : 02 : 31.725 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 2 08 - 06 12 : 02 : 32.725 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 3 08 - 06 12 : 02 : 33.726 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 4 08 - 06 12 : 02 : 34.726 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 5 08 - 06 12 : 02 : 35.726 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 6 08 - 06 12 : 02 : 36.727 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 7 08 - 06 12 : 02 : 37.727 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 8 08 - 06 12 : 02 : 37.980 28085 28085 D gatsby : The onPause() event 08 - 06 12 : 02 : 38.306 28085 28085 D gatsby : The onStop() event 08 - 06 12 : 02 : 38.307 28085 28085 D gatsby : The onDestroy() event 08 - 06 12 : 02 : 38.730 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 9 08 - 06 12 : 02 : 39.769 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 10 08 - 06 12 : 02 : 40.809 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 11 08 - 06 12 : 02 : 41.830 28085 28162 D gatsby : Thread.currentThread().getId()-> 336 i-> 1 08 - 06 12 : 02 : 41.832 28085 28085 D gatsby : The onStart() event 08 - 06 12 : 02 : 41.834 28085 28085 D gatsby : The onResume() event 08 - 06 12 : 02 : 41.849 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 12 08 - 06 12 : 02 : 42.831 28085 28162 D gatsby : Thread.currentThread().getId()-> 336 i-> 2 08 - 06 12 : 02 : 42.850 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 13 08 - 06 12 : 02 : 43.833 28085 28162 D gatsby : Thread.currentThread().getId()-> 336 i-> 3 08 - 06 12 : 02 : 43.852 28085 28103 D gatsby : Thread.currentThread().getId()-> 332 i-> 14 08 - 06 12 : 02 : 44.834 28085 28162 D gatsby : Thread.currentThread().getId()-> 336 i-> 4 |
线程对象属于一次性消耗品,一般线程执行完run方法之后,线程就正常结束了,线程结束之后就报废了,不能再次start,只能新建一个线程对象。但有时run方法是永远不会结束的。例如在程序中使用线程进行Socket监听请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。当需要结束线程时,如何退出线程呢?
有三种方法可以结束线程:
1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
2. 使用interrupt()方法中断线程
3. 使用stop方法强行终止线程(不推荐使用,可能发生不可预料的结果)
前两种方法都可以实现线程的正常退出,也就是要谈的优雅结束线程;第3种方法相当于电脑断电关机一样,是不安全的方法。
二、使用退出标志终止线程
使用一个变量来控制循环,例如最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。代码如下:
2.1. MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | package com.gatsby.threadsafe; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity implements View.OnClickListener { String TAG = "gatsby" ; Button btn1; static boolean isRunningCrush = false ; ThreadSafe threadSafe = null ; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "The onCreate() event" ); btn1 = (Button) findViewById(R.id.btn1); btn1.setOnClickListener( this ); threadSafe = new ThreadSafe( this ); threadSafe.CrushThread(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn1: isRunningCrush = false ; break ; } } /** * 当活动即将可见时调用 */ @Override protected void onStart() { super .onStart(); Log.d(TAG, "The onStart() event" ); } /** * 当活动可见时调用 */ @Override protected void onResume() { super .onResume(); Log.d(TAG, "The onResume() event" ); isRunningCrush = true ; } /** * 当其他活动获得焦点时调用 */ @Override protected void onPause() { super .onPause(); Log.d(TAG, "The onPause() event" ); } /** * 当活动不再可见时调用 */ @Override protected void onStop() { super .onStop(); Log.d(TAG, "The onStop() event" ); isRunningCrush = false ; } /** * 当活动将被销毁时调用 */ @Override public void onDestroy() { super .onDestroy(); Log.d(TAG, "The onDestroy() event" ); } } |
2.2.ThreadSafe.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import android.content.Context; import android.util.Log; public class ThreadSafe { private Context mContext; private Thread mThread; public ThreadSafe(Context context) { super (); this .mContext = mContext; } public void CrushThread() { MainActivity.isRunningCrush = true ; if (mThread == null ) { //开启线程 Log.d( "gatsby" , "mThread == null" ); mThread = new Thread( new Crush()); mThread.start(); } } } class Crush implements Runnable { int i = 1 ; @Override public void run() { while (MainActivity.isRunningCrush) { Log.d( "gatsby" , "Thread.currentThread().getId()->" + Thread.currentThread().getId() + " i->" + (i++)); try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值
三。使用interrupt()方法终止线程
使用interrupt()方法来终端线程可分为两种情况:
线程处于阻塞状态,如使用了sleep,同步锁的wait,socket的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,系统会抛出一个InterruptedException异常,代码中通过捕获异常,然后break跳出循环状态,使线程正常结束。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的,一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。
3.1. 线程正常状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.gatsby.threadsafe; import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); new MyThread().start(); } class MyThread extends Thread { int i = 1 ; @Override public void run() { while (i < 100 ) { Log.d( "gatsby" , "Thread.currentThread().getId()->" +Thread.currentThread().getId()+ " i->" + (i++)); try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); break ; //捕获到异常之后,执行break跳出循环。 } } } } } |
3.2.线程阻塞状态
线程未进入阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环,当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 定义开始和结束线程的方法,与按钮绑定 public void goThread() { if ( null == myThread) { myThread = new MyThread(); } myThread.start(); } private void stopThread() { if ( null != myThread && myThread.isAlive()) { myThread.interrupt(); myThread = null ; } } |
为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:
1 2 3 4 5 6 7 8 9 10 11 12 | public class ThreadSafe extends Thread { public void run() { while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出 try { Thread.sleep( 5 * 1000 ); //阻塞过程捕获中断异常来退出 } catch (InterruptedException e){ e.printStackTrace(); break ; //捕获到异常之后,执行break跳出循环。 } } } } |
3.3.线程正常运行状态、线程阻塞状态 一起处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public void goThread() { if ( null == myThread) { myThread = new MyThread(); } myThread.start(); } private void stopThread() { if ( null != myThread && myThread.isAlive()) { myThread.interrupt(); myThread = null ; } } public class MyThread extends Thread { @Override public void run() { super .run(); int i = 0 ; // 判断状态,如果被打断则跳出并将线程置空 while (!isInterrupted()) { Log.d( "gatsby" , "Thread.currentThread().getId()->" + Thread.currentThread().getId() + " i->" + (i++)); try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); //异常抛出,停止线程 Log.i( "gatsby" ,Thread.currentThread().getName()+ "" ); break ; } } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】