Android 線程終止的方法
前人:屌絲迷途 https://www.cnblogs.com/l2rf/p/5566895.html
Marker_Sky https://www.jianshu.com/p/49349eee9abc
Thread.currentThread().getName()
Thread.currentThread().getId()
先來看下問題
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);
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 多了一個
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
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
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. 線程正常狀態
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,和使用自定義的標志來控制循環是一樣的道理。
// 定義開始和結束線程的方法,與按鈕綁定
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()來退出線程的最好的方式應該是兩種情況都要考慮:
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標志來退出
try{
Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕獲到異常之后,執行break跳出循環。
}
}
}
}
3.3.線程正常運行狀態、線程阻塞狀態 一起處理
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;
}
}
}
}
