volatile标记位为何不适合于停止线程且与interrupt中止线程原理
Volatile解释
- volatile 是一个类型修饰符,作为一个指令关键字使用。如:
public volatile boolean is_stop = false;
volatile 保证修饰数据可见性,即所有对加的这个修饰词的变量的更新操作都能够被其他线程所看到。 - 从内存的角度来看,volatile在多处理器下,实现了缓存一致性协议,即保证各个处理器的缓存是一致的。具体实现是:
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
Volatile标记位实现停止线程
借用volatile的缓存一致性,将volatile修饰的数据看成flag来实现停止线程,如下的is_stop:
class test extends Thread{
public volatile boolean is_stop = false;
StorageDemo storage = new StorageDemo();
public void run() {
while(!is_stop) {
storage.put(50); //如果这里阻塞/休眠/等待会怎么杨
System.out.println(storage.getNum());
}
}
}
Storage类:
class StorageDemo{
int num=0;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public void put(int addNum) {
//200为库满
if(num<200) {
num+=addNum;
System.out.println(addNum);
}
}
}
主函数:
test t = new test();
t.start();
Thread.sleep(1000);
t.is_stop=true;
System.out.println("has been done");
结果显示:
这样通过volatile实现了,终止线程的操作。但是值得注意的是volatile只适合**
一
定
条
件
下
\color{red}{一定条件下}
一定条件下**的终止线程。
非一定条件下的volatile标记位
当循环中的语句陷入阻塞/休眠/等待,比如:storage.put(50);将会怎么样?volatile关键字还会起作用吗?
//将storage的put方法中休眠很长一段时间
if(num<200) {
num+=addNum;
System.out.println(addNum);
}
try {
Thread.sleep(4000000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
- 答案是不会
- 其效果为如下:注意到先右边线程并未停止。
为什么不会其作用呢?关键在于run方法中:
while(!is_stop) {
storage.put(50); // 就在这里被put方法被阻塞一直睡眠/阻塞/等待
System.out.println(storage.getNum());
}
只有当put方法执行完,volatile修饰的is_stop才会起作用将线程终止。但是如果陷入死锁,或其它等待时间过长,那将不会终止,直至put方法执行完毕。这样如果被阻塞时间很长,明显是不合适的。这时针对这种情况就需要interrupt了。
Interrupt中止线程原理
- interrupt(中断)是在线程对象做一个标记而已,称为中断标志。这个标记平常线程并不会去检测,( 前 提 \color{red}{前提} 前提)只有当线程陷入wait、join、yield、sleep这些阻塞情况的时候才会去不断检查中断标记,然后去中止线程。
- 但是值得注意的是中止与终止二者含义并不相同,中止是:这些被阻塞的线程检查到自己的中断标志为true,就会抛出InterruptException异常,进行捕获处理,从而达到中止的目的。效果如下:
- 注意到线程并未中终止,仅仅只是中止。