java 可中断线程

本文介绍了Java中三种实现线程可中断的方法:1) 使用Future和线程池,通过Future的cancel方法来取消任务;2) 利用共享变量进行中断标志检查;3) 直接调用线程的interrupt方法。通过这些方式,可以在执行过程中终止线程,避免资源浪费。

1、使用Future和线程池

JDK中关于Future的定义如下


/*
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

/*
 *
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

package java.util.concurrent;

/**
 * A {@code Future} represents the result of an asynchronous
 * computation.  Methods are provided to check if the computation is
 * complete, to wait for its completion, and to retrieve the result of
 * the computation.  The result can only be retrieved using method
 * {@code get} when the computation has completed, blocking if
 * necessary until it is ready.  Cancellation is performed by the
 * {@code cancel} method.  Additional methods are provided to
 * determine if the task completed normally or was cancelled. Once a
 * computation has completed, the computation cannot be cancelled.
 * If you would like to use a {@code Future} for the sake
 * of cancellability but not provide a usable result, you can
 * declare types of the form {@code Future<?>} and
 * return {@code null} as a result of the underlying task.
 *
 * <p>
 * <b>Sample Usage</b> (Note that the following classes are all
 * made-up.)
 * <pre> {@code
 * interface ArchiveSearcher { String search(String target); }
 * class App {
 *   ExecutorService executor = ...
 *   ArchiveSearcher searcher = ...
 *   void showSearch(final String target)
 *       throws InterruptedException {
 *     Future<String> future
 *       = executor.submit(new Callable<String>() {
 *         public String call() {
 *             return searcher.search(target);
 *         }});
 *     displayOtherThings(); // do other things while searching
 *     try {
 *       displayText(future.get()); // use future
 *     } catch (ExecutionException ex) { cleanup(); return; }
 *   }
 * }}</pre>
 *
 * The {@link FutureTask} class is an implementation of {@code Future} that
 * implements {@code Runnable}, and so may be executed by an {@code Executor}.
 * For example, the above construction with {@code submit} could be replaced by:
 *  <pre> {@code
 * FutureTask<String> future =
 *   new FutureTask<String>(new Callable<String>() {
 *     public String call() {
 *       return searcher.search(target);
 *   }});
 * executor.execute(future);}</pre>
 *
 * <p>Memory consistency effects: Actions taken by the asynchronous computation
 * <a href="package-summary.html#MemoryVisibility"> <i>happen-before</i></a>
 * actions following the corresponding {@code Future.get()} in another thread.
 *
 * @see FutureTask
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface Future<V> {

    /**
     * Attempts to cancel execution of this task.  This attempt will
     * fail if the task has already completed, has already been cancelled,
     * or could not be cancelled for some other reason. If successful,
     * and this task has not started when {@code cancel} is called,
     * this task should never run.  If the task has already started,
     * then the {@code mayInterruptIfRunning} parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.
     *
     * <p>After this method returns, subsequent calls to {@link #isDone} will
     * always return {@code true}.  Subsequent calls to {@link #isCancelled}
     * will always return {@code true} if this method returned {@code true}.
     *
     * @param mayInterruptIfRunning {@code true} if the thread executing this
     * task should be interrupted; otherwise, in-progress tasks are allowed
     * to complete
     * @return {@code false} if the task could not be cancelled,
     * typically because it has already completed normally;
     * {@code true} otherwise
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * Returns {@code true} if this task was cancelled before it completed
     * normally.
     *
     * @return {@code true} if this task was cancelled before it completed
     */
    boolean isCancelled();

    /**
     * Returns {@code true} if this task completed.
     *
     * Completion may be due to normal termination, an exception, or
     * cancellation -- in all of these cases, this method will return
     * {@code true}.
     *
     * @return {@code true} if this task completed
     */
    boolean isDone();

    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     * @throws TimeoutException if the wait timed out
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Cancellation is performed by the {@code cancel} method.  Additional methods are provided to determine if the task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled

红色字体可以看出,Future是可以取消的,只要对应的计算任务没有完成。下面是一个建议的例子,可以看到Future和线程池联合使用,即可实现可中断的线程。新定义的线程中封装了JDK的Runnable,对应的成员为task,同时加入类型为Future的cancel成员,当调用自定义线程的start()方法时,将task提交给线程池执行,同时获取返回值赋给cancel。之后可以通过自定义线程的cancel()方法,取消线程的执行,也就是调用成员cancel中的cancel()函数。

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package lpmoon;

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;

/**
 *
 * @author lpmoon
 */
public class InterruptableThread {

    class MyThread {

        Runnable task;
        Future cancel;
        ScheduledExecutorService stpe = Executors.newSingleThreadScheduledExecutor();

        MyThread(Runnable task) {
            this.task = task;
        }

        public void start() {
            cancel = stpe.submit(this.task);
            if (!stpe.isShutdown()) {
                stpe.shutdown();
            }
        }

        public void cancel() {
            cancel.cancel(true);
            if (!stpe.isShutdown()) {
                stpe.shutdown();
            }
        }
    }

    public void test() {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread start!");
                    Object synObj = new Object();
                    synchronized (synObj) {
                        synObj.wait(10000);
                    }
                    System.out.println("Thread end!");
                } catch (InterruptedException ex) {
                }
            }
        };

        MyThread myThread = new MyThread(run);
        myThread.start();
    }

    public static void main(String[] args) {
        InterruptableThread test = new InterruptableThread();
        test.test();
    }
}
执行上面的代码,在Netbeans中可以得到如下结果,

run:
Thread start!
Thread end!
成功构建 (总时间: 10 秒)

修改test函数,为如下所示
    public void test() {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread start!");
                    Object synObj = new Object();
                    synchronized (synObj) {
                        synObj.wait(10000);
                    }
                    System.out.println("Thread end!");
                } catch (InterruptedException ex) {
                }
            }
        };

        MyThread myThread = new MyThread(run);
        myThread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
        }
        myThread.cancel();
    }

执行上面的代码,在Netbeans中可以得到如下结果,

run:
Thread start!
成功构建 (总时间: 5 秒)

线程在执行的过程中被终止。


Future还有一个特殊的用法,可以实现定时任务的取消。很多人可能遇到过如下情况,使用ThreadPoolExecutor(Executors.newSingleThreadScheduledExecutor()或者Executors.newScheduledThreadPool (corePoolSize))实现定时任务的时候,有时需要终止定时任务,而在某个时间有需要重启定时任务。而在ThreadPoolExecutor中只有shutdown能够停止当前的定时任务,但是在上面的业务场景中意味着线程池需要不停的终止,初始化,造成了极大的资源浪费。而使用Future可以巧妙地解决这个问题。

代码如下所示

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package lpmoon;

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 *
 * @author lpmoon
 */
public class InterruptableThread {

    public static void main(String[] args) {
        ScheduledExecutorService stse = Executors.newSingleThreadScheduledExecutor();
        Future cancel = stse.scheduleAtFixedRate(new Runnable(){
            @Override
            public void run() {
                System.out.println("1");
            }
        }, 0, 1, TimeUnit.SECONDS);
        
        cancel.cancel(true);
    }
}

2、使用共享变量

也就是添加一个标志位,在取消的时候,改变标志位,等到线程循环到该标志位时,跳出。

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package lpmoon;

/**
 *
 * @author lpmoon
 */
public class InterruptableThread implements Runnable{

    private volatile boolean cancelled = false;
    private int i = 0;
    
    @Override
    public void run() {
        while(!cancelled){
            System.out.println(i++);
        }
    }
    
    public void cancel(){
        cancelled = true;
    }
    
    public static void main(String[] args){
        InterruptableThread test = new InterruptableThread();
        new Thread(test).start();
        test.cancel();
    }
}

但是,这种方法有一定的限制。适用于那些,需要通过循环不停的往下执行的任务,只有这样才能使标志位起到作用。而且共享变量的使用,需要保证在各个线程之间的可见性。因为cancel函数的调用可能在另外一个线程中,修改cancelled后,由于JMM的特殊性可能无法保证工作线程可以看到该值得改变,导致线程无法立刻终止。

还有值得注意的是,在这里共享变量定义为了volatile,之所以使用volatile关键字是因为其不但保证了可见性,而且相比于synchronized和ReentrantLock更加轻量级。虽然synchronized有轻量锁和偏向锁,但是使用不当的时候很可能会加重为重量锁。

3、使用中断

中断的方法和上面的共享变量的方法相类似,不过不同的是:使用共享变量,我们需要手动的保证其在不同线程之间的可见性,也就是保证并发情况下不会出错。而使用中断JDK会保证并发的正确性。

需要注意的是:



摘自<<Java Concurrency in Practice>> 
调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。对于中断操作的正确理解是:它并不会真正额中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中观自己。(这些时刻也被称为取消息点)。有些方法,例如wait、sleep和join等,将严格地处理这中请求,当他们收到中断请求或者在开始执行时发现某个已经设置好的中断状态时,将抛出一个异常。设计良好的方法可以完全忽略这种请求,还要它们能使调用代码对中断请求进行某种处理。。设计糟糕的方法完全可能会屏蔽中断请求,从而导致调用栈中的其他代码无法对中断请求作出响应。



摘自<<Java Concurrency in Practice>> 通常,中断是是瞎按取消的最合理方法。


相关的三个函数如下,
    /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    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();
    }

    /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }


完成和2中一样的功能,代码如下
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package lpmoon;

/**
 *
 * @author lpmoon
 */
public class InterruptableThread extends Thread {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("1");
        }
    }

    public void cancel() {
        interrupt();
    }

    public static void main(String[] args) {
        InterruptableThread test = new InterruptableThread();
        test.start();
        test.cancel();
    }
}



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值