Java中停止线程需要从下面三个方面进行考虑;
- 停止线程的正确方式是使用中断
- 想停止线程需要停止方,被停止方,被停止方的子方法相互配合
- 扩展到常见的错误停止线程方法:已被废弃的stop/suspend,无法唤醒阻塞线程的volatile
正确方式是中断
A. 从外部直接调用该线程的stop方法,直接把线程停下来(此种方式已经被废弃掉)。
B. 从外部通过中断通知线程停止,然后切换到被停止的线程,该线程执行一系列逻辑后自己停止。
很明显B方法要比A方法好很多,A方法太暴力了,你根本不知道被停止的线程在执行什么任务就直接把他停止了,程序容易出问题;而B方法把线程停止交给线程本身去做,被停止的线程可以在自己的代码中进行一些现场保护或者打印错误日志等方法再停止,更加合理,程序也更具健壮性。
下面要讲的是线程如何能够响应中断,第一个方法是通过循环不断判断自身是否产生了中断:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv_text_demo);
Thread thread = new Thread( new MyRunnalbe(),"TEXT" );
//开启线程;
thread.start();
try {
thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断贤臣
thread.interrupt();
}
public class MyRunnalbe implements Runnable{
int num =0;
@Override
public void run() {
while (num<=Integer.MAX_VALUE/2 && !Thread.currentThread().isInterrupted()){
if (num%1000==0){
Log.e("dbcxxx",num+"");
}
num++;
}
}
}
在上面的代码中,我们在循环条件中不断判断线程本身是否产生了中断,如果产生了中断就不再打印
还有一个方法是通过java内定的机制响应中断:
当线程调用sleep(),wait()方法后进入阻塞后,如果线程在阻塞的过程中被中断了,那么线程会捕获或抛出一个中断异常,我们可以根据这个中断异常去控制线程的停止。具体代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv_text_demo);
Thread thread= new Thread( new Demo3(),"TEXT" );
//开启线程;
thread.start();
try {
thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断贤臣
thread.interrupt();
}
public class Demo3 implements Runnable {
@Override
public void run() {
int num = 0;
try {
while (num < Integer.MAX_VALUE / 2) {
if (num % 100 == 0) {
System.out.println(num);
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {//捕获中断异常,在本代码中,出现中断异常后将退出循环
e.printStackTrace();
Log.e("dbcxxx",Thread.currentThread().getName()+"线程被中断");
}
}
}
各方配合才能完美停止
在上面的两段代码中已经可以看到,想通过中断停止线程是个需要多方配合。上面已经演示了中断方和被中断方的配合,下面考虑更多的情况:假如要被停止的线程正在执行某个子方法,这个时候该如何处理中断?
有两个办法:第一个是把中断传递给父方法,第二个是重新设置当前线程为中断。
先说第一个例子:在子方法中把中断异常上抛给父方法,然后在父方法中处理中断:
Thread thread= new Thread( new Demo4(),"TEXT" );
//开启线程;
thread.start();
try {
thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
thread.interrupt();
public class Demo4 implements Runnable {
@Override
public void run() {
try{//在父方法中捕获中断异常
while(true){
System.out.println("go");
throwInterrupt();
}
}catch (InterruptedException e) {
e.printStackTrace();
Log.e("dbcxxx",Thread.currentThread().getName()+"检测到中断,保存错误日志");
}
}
private void throwInterrupt() throws InterruptedException {//把中断上传给父方法
Thread.sleep(2000);
}
}
第二个例子:在子方法中捕获中断异常,但是捕获以后当前线程的中断控制位将被清除,父方法执行时将无法感知中断。所以此时在子方法中重新设置中断,这样父方法就可以通过对中断控制位的判断来处理中断:
public class Demo5 implements Runnable{
@Override
public void run() {
while(true && !Thread.currentThread().isInterrupted()){//每次循环判断中断控制位
System.out.println("go");
throwInterrupt();
}
System.out.println("检测到了中断,循环打印退出");
}
private void throwInterrupt(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//重新设置中断
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Demo5());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
interrupted() 和 isInterrupted()的区别
最后谈谈 interrupted() 和 isInterrupted()。
interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。
区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。