zoukankan      html  css  js  c++  java
  • Java 线程的中断机制

    今天我们聊聊 Java 线程的中断机制。

    线程中断机制提供了一种方法,用于将线程从阻塞等待中唤醒,并作出相应的“受控中断”处理。

    synchronized (lock) {
        try {
            while (!check()) {
                lock.wait(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    这段代码使用了 Java 提供的 wait/notify 机制,线程执行 lock.wait() 会阻塞,有三种情况使线程恢复运行。

    1. 超时 1000ms 结束,正常执行下一句代码。

    2. 另一个线程执行下述代码主动唤醒

      synchronized (lock) {
          lock.notifyAll(); // or lock.notify();
      }

      这也会正常执行下一句代码。

    3. 另一个线程要求等待的线程“中断”

      // 拿到等待中的线程的引用
      Thread a;
      a.interrupt();

      被“中断”的线程 a,会在 lock.wait() 处抛出 InterruptedException 异常。

    综上所述,你可以认为 object.wait() 内部在做这些事:

    boolean checkTimeout = timeout > 0;
    Thread current = Thread.currentThread();
    lock.addWaiter(current);
    while (!current.isNotified()) {
        if (current.isInterrupted()) {
            current.clearInterrupted();
            throw new InterruptedException();
        }
        if (checkTimeout) {
            if (timeout == 0) break;
            timeout--;
        }
    }

    这不完全准确,因为 wait 不使用这种“忙轮询”的方式做检查,但关于标志位的判断逻辑是正确的。

    让我们从手动中断开始探究,

    // sun.nio.ch.Interruptible
    
    public interface Interruptible {
        void interrupt(Thread var1);
    }
    
    // java.lang.Thread
    
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();
    
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
    
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
    
    // Just to set the interrupt flag
    private native void interrupt0();

    能够看出, thread.interrupt() 先判断权限,然后实际调用 interrupt0() 设置线程的中断标志,如果当前线程有 nio 的 Interruptible 那么还会回调它。

    注意,interrupt0() 只是设置了线程的中断标志。

    一个线程怎么知道自己被打断了?

    // java.lang.Thread
    
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    private native boolean isInterrupted(boolean clearInterrupted);

    也就是说, isInterrupted(boolean) 会返回线程是否被打断,并根据需要清空中断标志。

    我们发现,当一个线程并不阻塞,没有在 object.wait() , thread.join() , Thread.sleep()等不受 Java 程序逻辑控制的区域时,那么线程是否被打断只能通过检查中断标志得知。

    当一个函数调用可能阻塞,Java 会在阻塞的源头签名里标记 throws InterruptedException ,并要求编写 try catch 处理中断。

    当线程阻塞,就像上文所述,Java 检查到中断标志,先将其清除,然后抛出 InterruptedException 。

    // java.lang.Object
    
    public final void wait() throws InterruptedException {
        wait(0);
    }
    
    public final native void wait(long timeout) throws InterruptedException;

    如果一个线程收到 InterruptedException ,之后仍然执行了会引发阻塞的代码,它将像“没事人”一样继续阻塞住。因为 Java 在内部将中断标志清除了!

    我们常见地编写以下三类处理 InterruptedException 的代码:

    将 InterruptedException 交由上层处理。

    public void foo() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }

    遇到 InterruptedException 重设中断标志位。

    try {
        synchronized (lock) {  
            lock.wait();  
        }  
    } catch (InterruptedException e) {  
        Thread.currentThread().interrupt();
        //break; 
    }

    先忙完,再重新抛出 InterruptedException 。

    public void bar() throws InterruptedException {
        InterruptedException ie = null;
        boolean done = false;
        while (!done) {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    ie = e;
                    continue;
                }
            }
            done = true;
        }
        if (ie != null) {
            throw ie;
        }
    }

    如果一个线程无视中断标志和 InterruptedException ,它仍然能够跑的很好。但 这与我们设计多线程的初衷是违背的 ,我们希望线程之间是和谐的有序协作以实现特定功能,因此 受控线程应当对中断作出响应 。而 Java 留给开发者这一自由,我们应当予以善用。如果你想学习java可以加我的学习群669823128

     

  • 相关阅读:
    Linux下批处理文件
    linux代码端启动终端
    ubuntu截图
    Ubuntu安装多个版本的Opencv
    Ubuntu双系统启动卡死
    Ubuntu14.04运行lsdslam与问题解决
    js懒加载
    公众号开发-获取用户信息
    ClipboardJS 复制文本功能
    css3 Gradient线性渐变详解
  • 原文地址:https://www.cnblogs.com/rese-t/p/8012700.html
Copyright © 2011-2022 走看看