您的位置:首页 > 编程语言 > Java开发

Java多线程-通过线程的中断来深入学习interrupt方法,Volatile关键字

2017-10-25 19:42 691 查看


一.理解中断.

目前我用到线程的情景:第一是处理耗时的操作.第二是在后台循环的为我们去处理一些事情.用到循环去处理事情的时候线程该怎么中断呢?

大家都知道当线程的run方法走完或者run方法里面出现异常没有被捕获的时候,线程将终止.Jva在早期的时候曾提供过一个stop的方法用来打断线程,不过这个方法到现在已经被放弃了,不过又提供了一个interrupt方法来打断线程.听起来是不是感觉有希望了,可是结果呢往往

不尽人意.先来看下不过又提供了一个interrupt方法来打断线程吧!

interrupt

线程中会有一个默认的中断标识位,默认值为fasle,线程再运行的时候会经常去访问这个标识位.来判断线程是否会被终止.我们可以调用Thread.currentThread().isInterrupt()方法来获取这个标记位.如下所示:

int i =0;
while (!Thread.currentThread().isInterrupted()){
i++;
Log.e("CHAO","线程1 ->"+i);
}


还可以调用Thread.interrupted()来对中断标识位进行复位.当需要中断的时候可以调用interrupt()方法来打断线程.

Thread.currentThread().interrupt();


这里就要再说几点了,当线程处于活跃状态,调用该方法的时候线程不一定会终止,中断线程只是为了引起线程的注意,线程自己会去做决定去响应中断.如果是一些比较重要的线程将不会理会中断.另外就是当线程处于阻塞状态或者是睡眠状态的时候,调用interrupt()方法时将会抛出异常InterruptedException,然后将中断标识位设置为fasle.这一点很重要,我们可以利用这一点去终止发生阻塞的线程.另外一点就是,尽量不要再捕获到异常时不进行任何的处理,通常我们会将异常在命令行打印异常信息在程序中出错的位置及原因,也就是e.printStackTrace().我们可以次来设计一个合理打断阻塞线程或者睡眠中的线程的方法.

try {
//  Thread.sleep(2000);
int i =0;
while (! Thread.currentThread().isInterrupted()){
i++;
Log.e("CHAO","线程1 ->"+i);
}
} catch (Exception e) {
Log.e("CHAO","线程1            Exception ->"+e);
Thread.currentThread().interrupt();
e.printStackTrace();
}

这样是不会打断的时候是不会引起异常的.
===========================================================================

//模拟打断阻塞或者睡眠中的线程
try {
int i =0;
//注意这里的循环判断标志
while (! Thread.currentThread().isInterrupted()) {
i++;
Thread.sleep(2000);
Log.e("CHAO", "线程2执行打断线程1============================" +  i);
}
} catch (InterruptedException e) {
e.printStackTrace();
Log.e("CHAO","InterruptedException ->"+"");
//因为出现异常的时候会默认将中断标识位设置为fasle,
//所以这里需要在调用一次打断.结束线程
Thread.currentThread().interrupt();
}


Volatile关键字

既然使用interrupt方法不能客官的达到我们打断线程的效果,那大家伙一定见过这样的

例1:
//设置一个while循环结束的标记为.
boolean tage = true;

new Thread(){
@Override
public void run() {
super.run();
while (tage){
//do someThing
}

}
}.start();


这种打断是通过改变循环的标记位的值去让run方法走完已达到终止线程的效果.但这种情况在单线程中是百分之百没有任何问题呢,在多线程的情况下就不会那么的百分之百,通常情况下是可以的使用.下面就来分析下这样打断的方式不严谨的原因:

首先先来看下跟Java的内存模型有关的知识.Java内存模型的主要目标是定义了程序中各个变量的访问规则,就是在虚拟机中变量的存贮所涉及得到底层的细节.比较特殊的就是共享变量的问题(这里的变量也是指共享变量).Java内存模型所规定的变量都存储在主内存中,且每条线程都有自己的工作内存,工作内存保存的是该线程中要使用到的变量的从主内存中的副本拷贝,线程多变量的所有操作(读,写)都必须再工作内存中进行,不能直接对内存中的变量进行读写.不同线程之间也不能互相访问工作内存里面的内容,线程间变量的传递都需要通过主内存来实现.这里对Volatile关键字有特殊的处理方式,Volatile关键之修饰的变量依然可以共享主存的拷贝,但是由于他特殊的操作顺序性规定–从工作内存中读写数据前,必须先将主内存中的数据同步到工作内存中,所有看起来如同直接在主内存中读写访问一般,因此这里的描述对于volatile也不例外.

好了我们来看下例1的打断线程的例子,改中断标记为是不同的变量,所有他的读写都要遵循Java虚拟机的规范,也就是当有其他的线程改变tage的值得时候都需要把改变后的值重新写入主内存中,其他的线程才能从主存中获取改变后的值.但是一但出现修改tage值得线程修改变量之后并没有及时的去将该值写入主存中,那不就会出现错误吗,虽然何种情境是不多见的但是也是有的.怎么去避免这种特殊性的bug呢,接下来就来看下神奇的Volatile关键字.先来看下实现代码吧:

//这里的关键字是重点
private  volatile boolean mInterrupt = false;
mInterrupt = true;
thread1= new Thread(){
@Override
public void run() {
super.run();
int i =0;
while (mInterrupt){
i++;
Log.e("CHAO","线程1 ->"+i);
}
}
};

thread1.start();
//这里开始打断线程 在主线程中
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mInterrupt = false;
}
});
//运行结果,这里因为线程运行的特别的快,所以点击按钮的时候会有延迟.

10-24 22:18:44.851 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119770
10-24 22:18:44.851 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119771
10-24 22:18:44.851 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119772
10-24 22:18:44.851 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119773
10-24 22:18:44.852 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119774
10-24 22:18:44.852 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->119775
10-24 22:18:44.857 3180-3602/com.example.chao.threaddemo E/CHAO: 线程1 ->120125
10-24 22:18:46.968 3180-3180/com.example.chao.threaddemo E/CHAO:  interrupt ->==============================================


1.Volatile关键字的两层含义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义.

* 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的.也就是说当修改用volatile修饰的变量再修改过之后它会直接将修改后的值反应到内存中,不会受当前线程的约束,随后所有线程的工作线程中拥有这个的这一变量将失效.

* 禁止进行指令重排序.这里大概的叙述一下吧:

普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获得正确的结果,而不能保证变量赋值操作的顺序与程序中的执行顺序一致, 在单线程中,我们是无法感知这一点的.Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致,这个过程通过叫做指令的重排序.指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能.在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整.

二 就这样结束了

看到这里希望能对interrupt和volatile有一点新的认识,对于volatile关键字来说呢,这里只是粗略的说了一下为什么他可以用来作为线程的共享资源.内部的原理牵扯甚远.明天将讲一讲Java内存模型.

欢迎大家的吐槽,喜欢的话记得点赞哦~

明天预告: 浅谈Java的内存模型.

欢迎关注:我哦
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: