线程基础(二)线程的中断interrupt

JAVA中有3种方式可以终止正在运行的线程

①线程正常退出,即run()方法执行完毕了

②使用Thread类中的stop()方法强行终止线程。但stop()方法已经过期了,不推荐使用

③使用中断机制interrupt()

1.stop()方法

stop()在java多线程中已经废弃

1.stop()方法会导致释放锁的不良后果,数据不完整

比如一个上锁了得方法:

threadA线程拥有了监视器,这些监视器负责保护某些临界资源,比如说银行的转账的金额。当正在转账过程中,main线程调用 threadA.stop()方法或者this.stop()。结果导致监视器被释放,其保护的资源(转账金额)很可能出现不一致性。比如,A账户减少了100,而B账户却没有增加100,没有保证数据原子性

2.当线程调用stop()方法时,会立刻终止线程的所有操作,并抛出ThreadDeath异常,通常不需要捕捉

public class StopTest {
	static class MyThread extends Thread{
		@Override
		public void run() {
			synchronized(this){
				System.out.println("A转给B账户1000元");
				currentThread().stop();
				System.out.println("B收到A转给的1000元");
			}
		}
	}
	
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		t1.start();
	}
}
A转给B账户1000元

2.interrupt() 中断机制

  • 判断线程是否被中断

interrupt()不会真的中断一个正在运行的线程,而是给线程打一个停止的标记,还需要配合判断来优雅的终止线程

public class InterruptTest {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 500; i++) {
				System.out.println("count="+i);
			}
		}
	}
	
	public static void main(String[] args) {
			MyThread t1 = new MyThread();
			t1.start();
			t1.interrupt();
			System.out.println("调用线程中断方法");
	}
}
调用线程中断方法
count=0
count=1
count=2
count=3
count=4
count=5
count=6

interrupt() 并不会真正的停止正在运行的线程

3.判断线程的停止状态

interrupted() 是静态方法 而 isInterrupted() 是实例方法

interrupted()方法:判断当前线程是否已经中断

isInterrupted()方法:判断线程是否已经中断

public class InterruptTest {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 500000; i++) {
				System.out.println("count="+i);
			}
		}
	}
	
	public static void main(String[] args) {
		try {
			MyThread t1 = new MyThread();
			t1.start();
			Thread.sleep(1000);
			t1.interrupt();
			System.out.println("线程是否中断:"+t1.interrupted());
			System.out.println("线程是否中断:"+t1.interrupted());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
count=186950
count=186951
count=186952
count=186953
线程是否中断:false
线程是否中断:false
count=186954
count=186955

前面调用t1中断线程,后面判断是否中断,结果两次全为false未中断,说明interrupted()方法判断的当前线程不是t1而是main线程

修改上面的代码,中断main线程再判断

public class InterruptTest {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 100000; i++) {
				System.out.println("count="+i);
			}
		}
	}
	
	public static void main(String[] args) {
		try {
			MyThread t1 = new MyThread();
			t1.start();
			Thread.sleep(100);
			Thread.currentThread().interrupt();
			System.out.println("线程是否中断:"+t1.interrupted());
			System.out.println("线程是否中断:"+t1.interrupted());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
count=11429
count=11430
线程是否中断:true
线程是否中断:false
count=11431
count=11432
count=11433

结果第一个返回时true,第二个返回时false 说明interrupted()会调用 isInterrupted 方法 传入 参数clean 状态,调用该方法,该方法内部会清除中断状态

如下源码所示

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */ 
private native boolean isInterrupted(boolean ClearInterrupted);

修改上面的代码 改为isInterrupted()判断

public class InterruptTest {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 100000; i++) {
				System.out.println("count="+i);
			}
		}
	}
	
	public static void main(String[] args) {
		try {
			MyThread t1 = new MyThread();
			t1.start();
			Thread.sleep(100);
			t1.interrupt();
			System.out.println("线程是否中断:"+t1.isInterrupted());
			System.out.println("线程是否中断:"+t1.isInterrupted());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
count=12205
count=12206
线程是否中断:true
线程是否中断:true
count=12207
count=12208
count=12209

结果返回两个true证明isInterrupted()并没有清除状态标志。

  • 终止java线程

上面的示例说明线程的interrupt()方法并不能立即终止线程,那么如果我们要中断该线程怎么办呢

public class InterruptTest2 {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 500000; i++) {
				System.out.println("count="+i);
				if(this.interrupted()){		//判断当前线程是否被中断
					System.out.println("main线程命令中断线程");
					break;
				}
			}
			System.out.println("循环中断后。。。");
		}
	}
	
	public static void main(String[] args) {
		try {
			MyThread t1 = new MyThread();
			t1.start();
			Thread.sleep(2000);//main线程休眠2秒
			t1.interrupt();	//中断该线程
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
count=384552
count=384553
count=384554
count=384555
count=384556
main线程命令中断线程
循环中断后。。。

上诉代码运行结果可以看出当循环内判断该线程已经中断后,就通过break跳出循环。同时 最后一句循环中断后也会继续输出,因为break只是跳出该循环,并没有结束该线程。

修改上面的代码将break换成return

static class MyThread extends Thread{
		@Override
		public void run() {
			for (int i = 0; i < 500000; i++) {
				System.out.println("count="+i);
				if(this.interrupted()){		//判断当前线程是否被中断
					System.out.println("main线程命令中断线程");
					return;
				}
			}
			System.out.println("循环中断后。。。");
		}
	}
count=364980
count=364981
count=364982
count=364983
main线程命令中断线程

此时最后一句话确实没有执行。

但是使用return也不好,因为无法将事件传播

优雅的解决

抛异常方法 抛出InterruptedException异常

修改上诉代码

public class InterruptTest2 {
	
	static class MyThread extends Thread{
		@Override
		public void run() {
			try {
				for (int i = 0; i < 500000; i++) {
					System.out.println("count="+i);
					if(this.interrupted()){		//判断当前线程是否被中断
						System.out.println("main线程命令中断线程");
						throw new InterruptedException();
					}
				}
				System.out.println("循环中断后。。。");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		try {
			MyThread t1 = new MyThread();
			t1.start();
			Thread.sleep(2000);//main线程休眠2秒
			t1.interrupt();	//中断该线程
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
count=380876
count=380877
count=380878
count=380879
main线程命令中断线程
java.lang.InterruptedException
	at com.xuxu.b_thread_interrupt.InterruptTest2$MyThread.run(InterruptTest2.java:13)

java.lang.InterruptedException
    at com.xuxu.b_thread_interrupt.InterruptTest2$MyThread.run(InterruptTest2.java:13)

上面就是一个采用抛出异常的方式来结束线程的示例。尽管该示例的实用性不大。原因是我们用e.printStackTrace();生吞了中断

 由于this.interrupted()在第一次调用后清楚中断状态为false。仅仅记录 InterruptedException 也不是明智的做法,因为等到人来读取日志的时候,再来对它作出处理就为时已晚了。

有时候抛出 InterruptedException 并不合适,例如当由 Runnable 定义的任务调用一个可中断的方法时,就是如此。在这种情况下,不能重新抛出 InterruptedException,但是您也不想什么都不做。当一个阻塞方法检测到中断并抛出 InterruptedException 时,它清除中断状态。如果捕捉到 InterruptedException 但是不能重新抛出它,那么应该保留中断发生的证据,以便调用栈中更高层的代码能知道中断,并对中断作出响应。该任务可以通过调用 interrupt() 以 “重新中断” 当前线程来完成

2.因为,run方法是实现的Runnable接口中的方法。不能像下面这样定义,也即上面所说的:“不能重新抛出InterruptedException”。

   @Override
        public void run() throws InterruptedException{//这是错误的
          //do something...
将以上改为Thread.currentThread().interrupt();
	static class MyThread extends Thread{
		@Override
		public void run() {
			try {
				for (int i = 0; i < 500000; i++) {
					System.out.println("count="+i);
					if(this.interrupted()){		//判断当前线程是否被中断
						System.out.println("main线程命令中断线程");
						throw new InterruptedException();
					}
				}
				System.out.println("循环中断后。。。");
			} catch (InterruptedException e) {
//				e.printStackTrace();
				Thread.currentThread().interrupt();//这样处理比较好
			}
		}
	}

这样,就由 生吞异常 变成了 将 异常事件 进一步扩散了。 保留了中断的证据

ps 

对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号interrupt()后, 会抛出InterruptedException。就不需要手动抛异常法了,只需要在捕获异常块再次标记中断即可

猜你喜欢

转载自blog.csdn.net/baiyan3212/article/details/86492275
今日推荐