背景:楼主在项目开发中遇到一个问题,在Activity中需要起一个子线程执行耗时任务,Activity退出onDestroy后,释放资源。刚好子线程执行耗时任务的时候需要访问被释放的资源,进而发生了空指针异常的崩溃。
原因:主线程和子线程是并发的,主线程Activity何时退出释放资源,子线程何时访问资源,两者时序不可控。
1)加锁可以吗?
主线程Activity退出 释放资源时候加锁,;子线程执行Runnable时加锁,并且子线程执行任务之前判断是否已经释放资源。
这样可能会导致ANR问题:万一子线程耗时任务很久,主线程退出Activity时由于锁被子线程持有一直等待,有ANR风险。
2) 不加锁,使用volatile关键字标记是否释放变量
主线程退出Activity时,volatile变量 设置为true代表已经释放。子线程的runnable 执行之前判断volatile变量的值,如果为true代表资源是否,不再执行。
上线后依旧有零星崩溃上报:子线程的runnable判断volatile变量时候,资源还没释放。执行后续的逻辑,资源释放了造成崩溃。
时序: 子线程判断volatile变量,通过——主线程释放资源——子线程后续的逻辑访问资源
于是楼主想到,主线程退出Activity时,应该释放生命周期内使用的资源,包括子线程的Runnable,让Runnable不执行或者中断。——但是真的可以中断吗?
- 有人说Handler的removeCallbacksAndMessages——对于未执行的Runnable,可以,但是对于已经跑起来的Runnable, 不行。
- 有人说Thread.interrupt, 但是interrupt仅仅起到通知被停止线程的作用,实际上对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。——这个是Java 不提供强制停止线程机制。
- 有人说线程池的shutdownNow方法,但是shtdownNow方法本质也还是给正在执行的线程发送interrupt。
- 有人说使用线程池的Submit(Runnable runnable) 返回一个Future, 最后通过Future的cancel方法不就可以了??——楼主亲测,跑起来的Runnable 还是不行。
以上尝试,使用如下Demo验证, Activity Destroy后,Runnable依旧执行......
public class TestActivity extends Activity {
private Runnable runnable;
private HandlerThread handlerThread;
private Handler handler;
private Thread thread;
private Future future;
private ExecutorService executorService;
private Handler mainHandler = new Handler(Looper.getMainLooper());
public static void startActivity(Context context) {
Intent intent = new Intent(context, TestActivity.class);
context.startActivity(intent);
}
private void init() {
runnable = new Runnable() {
@Override
public void run() {
while (true) {
try{
Thread.sleep(1000);
Log.e("testing", "runnable run...." + TestActivity.this);
} catch (Exception e) {
Log.e("testing", "e run...." + e.toString());
}
}
}
};
//1. handlerThread
// handlerThread = new HandlerThread("test-handler-thread");
// handlerThread.start();
// handler = new Handler(handlerThread.getLooper());
// handler.post(runnable);
//2. Thread
// thread = new Thread(runnable);
// thread.start();
//3. future
executorService = Executors.newSingleThreadExecutor();
future = executorService.submit(runnable);
}
private void release() {
//1.handlerThread
// handlerThread.quit();
// handler.removeCallbacks(runnable);
//2. Thread
// try {
// thread.interrupt();
// } catch (Exception e) {
// }
//3. future
if(future != null) {
boolean ret = future.cancel(true);
executorService.shutdown();
Log.e("testing", "future cancel...." + ret);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Log.e("testing", "act onCreate ");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
init();
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TestActivity.this.finish();
}
});
}
@Override
protected void onDestroy() {
release();
Log.e("testing", "act onDestroy ");
super.onDestroy();
}
}
实际上,JAVA 推荐了使用Thread.currentThread().isInterrupted()的方法判断线程是否中断
也就是Demo中Runnable的 while(true) 修改为while(!Thread.currentThread().isInterrupted())
就Demo而言,可以实现中断Runnable, 但是实际项目的Runnable并不是一直循环,大多是一个耗费时间的任务。 Thread.currentThread().isInterrupted()判断不能一直贯穿整个Runnable, 只能在某一个时刻调用——万一在Runnable里调用isInterrupted()的时候线程还没interrupt, 调用isInterrupted()判断过后线程才interrupt, runnable仍然访问被释放的资源。