J.U.C_InterruptedException

  1. 前言
  2. java.lang.Thread#interrupt
  3. InterruptedException是什么
  4. 举个栗子
    1. Graceful stop
      1. 常见的做法
      2. 更为通用的做法
    2. shutdown 和 shutdownNow
      1. shutdown
      2. shutdownNow

https://image.ipaiban.com/upload-ueditor-image-20190608-1559957563271017590.jpg

前言

​ 前几天在看JSR-133的时候发现 ,Sleep和Yield ,然后忽然想起Thread 线程中有几个被@Deprecation 的方法,

主要有如下几个suspendedstopresume 官方的解释如下:

Why are Thread.suspend and Thread.resume deprecated?

Thread.suspend is inherently deadlock-prone. If the target thread holds a lock on the monitor protecting a critical system resource when it is suspended, no thread can access this resource until the target thread is resumed. If the thread that would resume the target thread attempts to lock this monitor prior to calling resume, deadlock results. Such deadlocks typically manifest themselves as “frozen” processes.

原因就是会有线程安全问题(死锁), suspended 期间还是会占有锁,更多相关

然后就看到了InterruptedException这个异常. 之前了解过,但是没有深入理解.所以就打算好好了解下记录

java.lang.Thread#interrupt

首先先来看看线程相关的方法,中断线程,

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

当然这个interrupt的作用只是把线程设置一个标记, Just to set the interrupt flag

至于这里为什么只是设置了一个标记, 在core java中有提到

There is no language requirement that a thread which is interrupted should ter- minate. Interrupting a thread simply grabs its attention. The interrupted thread can decide how to react to the interruption.

即什么时候终止, 应该由被中断的线程去处理,所以使用这个的场景一般配合Thread.currentThread().isInterrupted() 来判断标记位(注意标记 默认为 false),退出循环 ,但是如果中断的是wait、sleep 等阻塞状态等线程,则会抛出InterruptedException

InterruptedException是什么

直接看类的解释

package java.lang;

/**
 * Thrown when a thread is waiting, sleeping, or otherwise occupied,
 * and the thread is interrupted, either before or during the activity.
 * Occasionally a method may wish to test whether the current
 * thread has been interrupted, and if so, to immediately throw
 * this exception.  The following code can be used to achieve
 * this effect:
 * <pre>
 *  if (Thread.interrupted())  // Clears interrupted status!
 *      throw new InterruptedException();
 * </pre>
 *
 * @author  Frank Yellin
 * @see     java.lang.Object#wait()
 * @see     java.lang.Object#wait(long)
 * @see     java.lang.Object#wait(long, int)
 * @see     java.lang.Thread#sleep(long)
 * @see     java.lang.Thread#interrupt()
 * @see     java.lang.Thread#interrupted()
 * @since   JDK1.0
 */
public
class InterruptedException extends Exception {
    private static final long serialVersionUID = 6700697376100628473L;

    /**
     * Constructs an <code>InterruptedException</code> with no detail  message.
     */
    public InterruptedException() {
        super();
    }

    /**
     * Constructs an <code>InterruptedException</code> with the
     * specified detail message.
     *
     * @param   s   the detail message.
     */
    public InterruptedException(String s) {
        super(s);
    }
}

简单直译就是被中断的异常. 如果当前线程处于阻塞状态,sleep 、wait时候 被其他线程中断了,就会抛出该异常

下面我们再看看wait方法对其中的解释

 /**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

注意一点,wait会抛出两个异常,其中 IllegalMonitorStateException 是如果当前线程没有获取到对象的monitor,然后使用对象的wait方法时就会出现

另一个就是我们的InterruptedException 了 ,这里的解释也是和上面一样就不做重复赘述了.

但是需要注意的是有一句

The interrupted  status of the current thread is cleared when this exception is thrown.

也就是线程中断标记位会被重置 ,所以可以看到如果对这个异常不做处理,那么这个线程可能永远都不能被中断

举个栗子

 当我们在使用多线程并发调度的时候, (多线程的使用场景一般用在IO阻塞比较多的情况) 为了防止线程的频繁创建带来的损耗,一般会使用 `ThreadPoolExecutor`  
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
}

关于ThreadPoolExecutor的使用细节

当容器退出的时候,那么我们应该如何停止这个线程池的任务

Graceful stop

​ 既然提到了graceful ,在平时工作中,我们不能因为容器的退出,而导致运行中的任务执行失败,强制退出,一般会通过钩子方法

    Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        try {
            logger.info("stop the Executor....");
            shutDown();
        } catch (Exception e) {
            logger.error("shutDown error,msg:", e);
        }
    }
});

​ 在ThreadPoolExecutor中,我们可以使用 java.util.concurrent.ThreadPoolExecutor#shutdown 方法

但是对于下面的这种Runnable ,是停止不了的

public class Worker implements Runnable {
    @Override
    public void run() {
         while (true) {
             dosomething();
         }
    }
}

常见的做法

所以我们一般会通过设置一个条件变量判断

public class Worker implements Runnable {
    private volatile boolean stopFlag =  false;
    public void stop(){
        stopFlag = true;
    }
    @Override
    public void run() {
        while (stopFlag) {
            dosomething();
        }
    }
}

更为通用的做法

但是这种方式很麻烦,需要遍历线程池去调用stop方法,所以更为通用的方法是利用interrupted 标记来处理

public class Worker implements Runnable {
//        private volatile boolean stopFlag =  false;
//        public void stop(){
//            stopFlag = true;
//        }
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("i'm running " + Thread.currentThread().getName());

        }
    }
}

这个时候调用 java.util.concurrent.ThreadPoolExecutor#shutdown 会发现线程没有停下来,那么为什么标记位没有被设置~

shutdown 和 shutdownNow

shutdown

上面为什么没有停下来,debug进入shutdown

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

再进入interruptIdleWorkers,会发现遍历worker进行interrupt

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

发现 w.tryLock() 返回false ,这里取不到锁的原因是因为ThreadPoolExecutor 内部封装了Worker, Worker在运行的时候会调用w.lock();

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

所以,shutdown方法 是不会对运行中的线程设置标记位

shutdownNow

java.util.concurrent.ThreadPoolExecutor#shutdownNow , 注意这个方法并不是强制退出哈, 它和 shutdown区别如下

  • 只是它会对运行中的线程设置中断标记
  • 还有不再执行等待队列的任务,返回未执行任务
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
/**
 * Drains the task queue into a new list, normally using
 * drainTo. But if the queue is a DelayQueue or any other kind of
 * queue for which poll or drainTo may fail to remove some
 * elements, it deletes them one by one.
 */
private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

所以我们就可以愉快的使用shutdownNowThread.currentThread().isInterrupted()

最后再提醒一波,不要忘记对InterruptedException的处理


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:J.U.C_InterruptedException

字数:2.1k

本文作者:zhengyumin

发布时间:2019-06-16, 16:09:43

最后更新:2020-01-12, 23:08:48

原始链接:http://zyumin.github.io/2019/06/16/J-U-C-InterruptedException/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。