引子
1.为什么不用stop方法
不能使用Thread类的stop方法,使用stop方法,线程的安全性无法得到保证,即使是线程中使用了synchronized方法。
public synchronized void setXY(int newX,int newY){
x=newX;
y=newY
}
上边的例子用synchronized保证了只能一个线程访问setXY代码段,x y被一个线程先后更新。但是用stop方法,就会出现x被赋值为newX后,直接退出,这样y还没被赋值。
2.Interrupt方法的含义
Object.wait 、Thread sleep 、join 可以抛出InterruptedException,其他任何线程都可以调用任意线程的interrupt()方法,而不用获取该线程的锁,对wait的线程,调用interrupt后会重新获得锁然后抛出InterruptException异常。InterruptException异常是因为线程的中断状态发生了改变,线程检测到该变化抛出异常。抛出异常后中断状态被清除。
try catch 捕获到InterruptedException,中断状态被清除,执行的线程中就会忽略中断请求,判断中断标记isInterrupted就会无效。因此有下边几种情况。
响应中断请求的线程
package com.example.test;
// 在run方法中有sleep的两种写法
class HelloThread extends Thread {
public void run() {
// 这种写法是廖大神博客中的一种写法
int n = 0;
while (!isInterrupted()) {// 有Thread.sleep时判断中断,需要用break跳出循环
n++;
System.out.println("HelloThread执行!");
try {
Thread.sleep(100);// 当前线程正在等待Thread.sleep()等待,如果对其调用中断请求,就抛出InterruptedException异常,
// 就说明其他线程调用了此线程的interrupt方法,当前线程结束状态会被清除,必须跳出循环
} catch (InterruptedException e) {// 抛出异常会清除中断状态
System.out.println("HelloThread 线程在睡眠或者等待状态同时调用了该线程的interrupt方法,收到中断请求引发异常,捕获之后标志位重置为非中断状态");
System.out.println("HelloThread"+isInterrupted());// 捕获了异常isInterrupted()就不会变成true了
break;// 需要用break跳出循环
} finally {
System.out.println("HelloThread"+isInterrupted());// 捕获了异常isInterrupted()就不会变成true了
}
}
// 这种写法是java核心编程第一卷中的写法
// try {
// while(true){
// n++;
// System.out.println("HelloThread执行");
// Thread.sleep(10000);// 当前线程正在等待Thread.sleep()等待,如果对其调用中断请求,就抛出InterruptedException异常
// }
// }
// catch (InterruptedException e){
// System.out.println("HelloThread 线程在睡眠或者等待状态同时调用了该线程的interrupt方法");
// }finally {
// System.out.println("HelloThread "+isInterrupted());
// }
}
}
// 在run方法中没有sleep的写法
class HelloThread1 extends Thread {
public void run() {
int n = 0;
try {
while (!isInterrupted()) {
n++;
System.out.println(n + "HelloThread1 hello!");
System.out.println(Thread.currentThread().getName() + "HelloThread1 没有收到中断请求");
}
} catch (Exception e) {
System.out.println("HelloThread1发生异常");
} finally {
System.out.println("HelloThread1 最终结果");
}
}
}
// 如果是join,测试中断
class MyThread extends Thread {
public void run() {
HelloThread helloThread = new HelloThread();
helloThread.start();
try {
System.out.println( "MyThread 执行");
helloThread.join();// 让当前进程等待,当前线程正在等待,抛出异常,就说明其他线程调用了此线程的interrupt方法,该线程就该被中断,并引发异常,进入catch语句中,并且isInterrupted被清除,状态为false;
System.out.println("MyThread wait");
} catch (InterruptedException e) {
System.out.println("MyThread wait 正在等待被中断了,验证中断异常isInterrupted被清除,状态为false"+isInterrupted());// 验证isInterrupted被清除,状态为false
}
helloThread.interrupt();// 引发HelloThread中的InterruptedException异常
}
}
public class TestThread {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
// myThread的中断状态isInterrupted()置为true,
//如果myThread正在等待或者阻塞状态(如调用currentThread.sleep,ortherThread.join,object.wait),
//对myThread调用interrupt(),会引发MyThread类中的异常
myThread.interrupt();
System.out.println("main wait");
myThread.join();
System.out.println("end");
}
}
知识点:
Tip1:volatile 原子操作赋值
java中规定对基本类型和引用类型的赋值和引用是原子操作(atom),但是对long和double类型没有约束,所以对long和double的赋值和引用,涉及到线程不安全,加上volatile 即
volatile long a=0L;
Tip2:volatile在单例模式下也有应用
// volatile在单例模式下也有应用
class VolatileSingleton {
private volatile static VolatileSingleton singleton;
private VolatileSingleton (){}
public static VolatileSingleton getSingleton() {
if (singleton == null) {
synchronized (VolatileSingleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Tip3:join和isAlive方法
join和isAlive方法,可以使用thread.join等待线程执行结束,也可以使用t.isAlive判断线程是否终止。或者使用thread.getState来判断,不过使用isAlive会更好。
中断线程的另外一种方式:volatile关键字修饰变量,作为中断请求标记,但是如果有sleep wait join不能立刻响应
// 廖大神博客中的另一种写法,使用volatile关键字保证读取的时效性,volatile修饰的成员变量Bealean型,可以保证一旦有线程改变了runningState,该线程马上能读到,并停止线程
class InterpretThreadDemo extends Thread {
public volatile Boolean runningState = true;
public void run() {
while (runningState) {
System.out.println("i am running!");
}
}
}
假设一个线程执行方法中不能判断是否有sleep wait等方法,有没有一种通用的方式来中断线程呢?
解决这个难题的方法在 《java图解多线程模式》有了解答,将两种方式结合起来:
1.仅仅检查中断状态是不够的,如果出现了
try{Thread.sleep(100);}catch(InterrupedException e){// 忽略InterruptedException}
这样的语句。即使判断了isInterrupted()也会被catch执行后清除中断状态(忽略中断),而导致判断isInterrupted()无必要也没用处。(和java核心编程中描述一致)。
2.仅仅检查被volatile修饰的停止请求状态标记是不够的,因为当线程正在sleep时,线程中断请求发出后,线程不会立即响应,而是sleep时间到再响应。如果线程执行,遇到wait,线程不会立即从等待队列中出来进行响应。时效性不行。
响应中断请求的线程
package com.example.TwoPhaseTermination;
public class WonderfulInterruptThread extends Thread{
private long count=0;// 计数
// 中断标记,指示是否发出中断请求,将interruptRequestFlag中断标记位封装起来,用方法赋值和获取
private volatile boolean interruptRequestFlag=false;
public void wonderfulInterrupt(){
interruptRequestFlag=true;// 保证没有sleep、wait、join时立即终止
interrupt();// 有sleep、wait、join时立即终止
}
public boolean isInterruptRequest(){// interruptRequestFlag,是否发出中断请求
return interruptRequestFlag;
}
public final void run(){
try {
while (!interruptRequestFlag){
doWork();
}
}catch (InterruptedException e){
}finally {
doFinalWork();
}
}
private void doWork() throws InterruptedException {// 循环一直做的工作
count++;
System.out.println("一直累加至 "+count);
Thread.sleep(500);
}
private void doFinalWork(){// 线程结束前,一定要执行的
System.out.println("最终输出累加数值 "+count);
}
}
主线程发出中断请求
package com.example.TwoPhaseTermination;
public class Main {
public static void main(String[] args) {
System.out.println("主线程开始");
try {
WonderfulInterruptThread t=new WonderfulInterruptThread();
t.start();
System.out.println("WonderfulInterruptThread执行");
System.out.println("主线程休眠");
Thread.sleep(6000);// 主线程休眠
System.out.println("主线程对WonderfulInterruptThread线程发出中断请求");
t.wonderfulInterrupt();
// 等待WonderfulInterruptThread结束
// t.join();
while (t.isAlive()){
}
System.out.println("WonderfulInterruptThread响应中断,执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
结果
主线程开始
WonderfulInterruptThread执行
主线程休眠
一直累加至 1
一直累加至 2
一直累加至 3
一直累加至 4
一直累加至 5
一直累加至 6
一直累加至 7
一直累加至 8
一直累加至 9
一直累加至 10
一直累加至 11
一直累加至 12
主线程对WonderfulInterruptThread线程发出中断请求
最终输出累加数值 12
WonderfulInterruptThread响应中断,执行完毕
主线程结束