线程中断分析

正如中断二字所表达的意义,在线程运行(run方法)中间打断它,在Java中,提供了以下3个有关线程中断的方法

//中断线程(实例方法)
public void Thread.interrupt();

//判断线程是否被中断(实例方法)
public boolean Thread.isInterrupted();

//判断是否被中断并清除当前中断状态(静态方法)
public static boolean Thread.interrupted();

当一个线程处于被阻塞状态或者试图执行一个阻塞操作时,使用Thread.interrupt()方式中断该线程,注意此时将会抛出一个InterruptedException的异常,同时中断状态将会被复位(由中断状态改为非中断状态),如下代码将演示该过程:

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputSleepThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                // while在try中,通过异常中断就可以退出run循环
                try {
                    while (true) {
                        // 当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interruted When Sleep");
                    boolean interrupt = this.isInterrupted();
                    // 中断状态被复位
                    System.out.println("interrupt:" + interrupt);
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        // 中断处于阻塞状态的线程
        t1.interrupt();
    }
}

结果:

Interruted When Sleep
interrupt:false

如上述代码所示,我们创建一个线程,并在线程中调用了sleep方法从而使用线程进入阻塞状态,启动线程后,调用线程实例对象的interrupt方法中断阻塞状态,并抛出InterruptedException异常,此时中断状态也将被复位。这里有些人可能会诧异,为什么不用Thread.sleep(2000);而是用TimeUnit.SECONDS.sleep(2);其实原因很简单,前者使用时并没有明确的单位说明,而后者非常明确表达秒的单位,事实上后者的内部实现最终还是调用了Thread.sleep(2000);,但为了编写的代码语义更清晰,建议使用TimeUnit.SECONDS.sleep(2);的方式,注意TimeUnit是个枚举类型。

除了阻塞中断的情景,我们还可能会遇到处于运行期且非阻塞的状态的线程,这种情况下,直接调用Thread.interrupt()中断线程是不会得到任响应的,如下代码,将无法中断非阻塞状态下的线程:

复制代码
package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("未被中断");
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

    }
}
复制代码

结果:

未被中断
未被中断
未被中断
...

虽然我们调用了interrupt方法,但线程t1并未被中断,因为处于非阻塞状态的线程需要我们手动进行中断检测并结束程序,改进后代码如下:

复制代码
package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 判断当前线程是否被中断
                    if (this.isInterrupted()) {
                        System.out.println("线程中断");
                        break;
                    }
                }

                System.out.println("已跳出循环,线程中断!");
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();
    }
}
复制代码

结果:

线程中断
已跳出循环,线程中断!

是的,我们在代码中使用了实例方法isInterrupted判断线程是否已被中断,如果被中断将跳出循环以此结束线程。综合所述,可以简单总结一下中断两种情况:

  • 一种是当线程处于阻塞状态或者试图执行一个阻塞操作时,我们可以使用实例方法interrupt()进行线程中断,执行中断操作后将会抛出interruptException异常(该异常必须捕捉无法向外抛出)并将中断状态复位;
  • 另外一种是当线程处于运行状态时,我们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)。有时我们在编码时可能需要兼顾以上两种情况,那么就可以如下编写:
    复制代码
    package com.dxz.synchronize;
    
    import java.util.concurrent.TimeUnit;
    
    public class InterruputThread3 {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    try {
                        // 判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
                        while (!Thread.interrupted()) {
                            System.out.println("服务中...");
                            TimeUnit.SECONDS.sleep(2);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("中断");
                        // 中断状态被复位
                        System.out.println("interrupt:" + this.isInterrupted());
                    }
                }
            };
            t1.start();
            TimeUnit.SECONDS.sleep(2);
            t1.interrupt();
        }
    }
    复制代码

结果:

服务中...
服务中...
中断
interrupt:false

中断与synchronized

事实上线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它就保存等待,即使调用中断线程的方法,也不会生效。演示代码如下

复制代码
package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class SynchronizedBlocked implements Runnable {

    public synchronized void f() {
        System.out.println("Trying to call f()");
        while (true) // Never releases lock
            Thread.yield();
    }

    /**
     * 在构造器中创建新线程并启动获取对象锁
     */
    public SynchronizedBlocked() {
        // 该线程已持有当前实例锁
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }

    public void run() {
        // 中断判断
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("中断线程!!");
                break;
            } else {
                f();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlocked sync = new SynchronizedBlocked();
        Thread t = new Thread(sync);
        // 启动后调用f()方法,无法获取当前实例锁处于等待状态
        t.start();
        TimeUnit.SECONDS.sleep(1);
        // 中断线程,无法生效
        t.interrupt();
    }
}

结果:

Trying to call f()

我们在SynchronizedBlocked构造函数中创建一个新线程并启动获取调用f()获取到当前实例锁,由于SynchronizedBlocked自身也是线程,启动后在其run方法中也调用了f(),但由于对象锁被其他线程占用,导致t线程只能等到锁,此时我们调用了t.interrupt();但并不能中断线程。

等待唤醒机制与synchronized

所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }

需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。


补充:

上述都是通过调用interrupt()方法进行中断,然后通过监测线程中的中断标志位或者捕获线程的方式来实现线程的终止。在实际的编程中也可以通过设置共享的变量的方式实现。

通过修改共享变量来通知目标线程停止运行
这种方法有几个要求或注意事项:
(1)目标线程必须有规律的检查变量,当该变量指示它应该停止运行时,该线程应该按一定的顺序从它执行的方法中返回。
(2)该变量必须定义为 volatile或者所有对它的访问必须同步(synchronized)。
 
例如:
复制代码
package chapter2; 

public class ThreadFlag extends Thread 
{ 
    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    } 
    public static void main(String[] args) throws Exception 
    { 
        ThreadFlag thread = new ThreadFlag(); 
        thread.start(); 
        sleep(5000); // 主线程延迟5秒 
        thread.exit = true;  // 终止线程thread 
        thread.join(); 
        System.out.println("线程退出!"); 
    } 
} 
复制代码

 在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值。

2.2 处于IO阻塞状态线程的停止

         Java中的输入输出流并没有类似于Interrupt的机制,但是Java的InterruptableChanel接口提供了这样的机制,任何实现了InterruptableChanel接口的类的IO阻塞都是可中断的,中断时抛出ClosedByInterruptedException,也是由Thread对象调用Interrupt方法完成中断调用。IO中断后将关闭通道。 

         实现InterruptableChanel接口的类包括FileChannel,ServerSocketChannel, SocketChannel, Pipe.SinkChannel andPipe.SourceChannel,也就是说,原则上可以实现文件、Socket、管道的可中断IO阻塞操作。

        虽然解除IO阻塞的方法还可以直接调用IO对象的Close方法,这也会抛出IO异常。但是InterruptableChanel机制能够使处于IO阻塞的线程能够有一个和处于中断等待的线程一致的线程停止方案。

3 处于大数据IO读写中的线程停止

         处于大数据IO读写中的线程实际上处于运行状态,而不是等待或阻塞状态,因此上面的interrupt机制不适用。线程处于IO读写中可以看成是线程运行中的一种特例。停止这样的线程的办法是强行close掉io输入输出流对象,使其抛出异常,进而使线程停止。

        最好的建议是将大数据的IO读写操作放在循环中进行,这样可以在每次循环中都有线程停止的时机,这也就将问题转化为如何停止正在运行中的线程的问题了。

4 在线程运行前停止线程

         有时,线程中的run方法需要足够健壮以支持在线程实际运行前终止线程的情况。即在Thread创建后,到Thread的start方法调用前这段时间,调用自定义的stop方法也要奏效。从上述的停止处于等待状态线程的代码示例中,stop方法并不能终止运行前的线程,因为在Thread的start方法被调用前,调用interrupt方法并不会将Thread对象的中断状态置位,这样当run方法执行时,currentThread的isInterrupted方法返回false,线程将继续执行下去。

         还有一种解决方法,也可以在run中直接使用该自定义标志量,而不使用isInterrupted方法判断线程是否应该停止。这种方法的run代码框架实际上和停止运行时线程的一。

package com.entity;

import java.util.concurrent.Callable;

public class InterruptCallable implements Runnable {
   public boolean isBeforeFlag() {
		return beforeFlag;
	}
	public void setBeforeFlag(boolean beforeFlag) {
		this.beforeFlag = beforeFlag;
	}
	public boolean isBetweenFlag() {
		return betweenFlag;
	}
	public void setBetweenFlag(boolean betweenFlag) {
		this.betweenFlag = betweenFlag;
	}
public volatile boolean beforeFlag = false;
   public volatile boolean betweenFlag = false;
	@Override
	public void run() {
		while(beforeFlag) {
			beforeFlag = false;
			System.out.println("false");
			while(!betweenFlag) {
				 betweenFlag = false;
				 System.out.println("true");
			}
		}	
	}

}

	@Test
	public void interruptCallable() {
		InterruptCallable interruptCallable = new InterruptCallable();
	    Thread thread = new Thread( interruptCallable);
		interruptCallable.setBeforeFlag(true);
		interruptCallable.setBetweenFlag(true);
		thread.start();
	}



猜你喜欢

转载自blog.csdn.net/yaoyaowudi123/article/details/80199039