挂起和恢复线程

suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此 时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就 会造成死锁。

所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

正确方式:

public class AlternateSuspendResume extends Object implements Runnable {

    private volatile int firstVal;
    private volatile int secondVal;
    //增加标志位,用来实现线程的挂起和恢复
    private volatile boolean suspended;

    public boolean areValuesEqual() {
        return ( firstVal == secondVal );
    }

    public void run() {
        try {
            suspended = false;
            firstVal = 0;
            secondVal = 0;
            workMethod();
        } catch ( InterruptedException x ) {
            System.out.println("interrupted while in workMethod()");
        }
    }

    private void workMethod() throws InterruptedException {
        int val = 1;

        while ( true ) {
            //仅当贤臣挂起时,才运行这行代码
            waitWhileSuspended(); 

            stepOne(val);
            stepTwo(val);
            val++;

            //仅当线程挂起时,才运行这行代码
            waitWhileSuspended(); 

            Thread.sleep(200);  
        }
    }

    private void stepOne(int newVal) 
                    throws InterruptedException {

        firstVal = newVal;
        Thread.sleep(300);  
    }

    private void stepTwo(int newVal) {
        secondVal = newVal;
    }

    public void suspendRequest() {
        suspended = true;
    }

    public void resumeRequest() {
        suspended = false;
    }

    private void waitWhileSuspended() 
                throws InterruptedException {

        //这是一个“繁忙等待”技术的示例。
        //它是非等待条件改变的最佳途径,因为它会不断请求处理器周期地执行检查, 
        //更佳的技术是:使用Java的内置“通知-等待”机制
        while ( suspended ) {
            Thread.sleep(200);
        }
    }

    public static void main(String[] args) {
        AlternateSuspendResume asr = 
                new AlternateSuspendResume();

        Thread t = new Thread(asr);
        t.start();

        //休眠1秒,让其他线程有机会获得执行
        try { Thread.sleep(1000); } 
        catch ( InterruptedException x ) { }

        for ( int i = 0; i < 10; i++ ) {
            asr.suspendRequest();

            //让线程有机会注意到挂起请求
            //注意:这里休眠时间一定要大于
            //stepOne操作对firstVal赋值后的休眠时间,即300ms,
            //目的是为了防止在执行asr.areValuesEqual()进行比较时,
            //恰逢stepOne操作执行完,而stepTwo操作还没执行
            try { Thread.sleep(350); } 
            catch ( InterruptedException x ) { }

            System.out.println("dsr.areValuesEqual()=" + 
                    asr.areValuesEqual());

            asr.resumeRequest();

            try { 
                //线程随机休眠0~2秒
                Thread.sleep(
                        ( long ) (Math.random() * 2000.0) );
            } catch ( InterruptedException x ) {
                //略
            }
        }

        System.exit(0); //退出应用程序
    }
}

可以看出程序运行良好

错误方式:

public class MyThreadDemo extends Object implements Runnable {

    // volatile关键字,表示该变量可能在被一个线程使用的同时,被另一个线程修改
    private volatile int firstVal;
    private volatile int secondVal;

    // 判断二者是否相等
    public boolean areValuesEqual() {
        return (firstVal == secondVal);
    }

    @Override
    public void run() {
        try {
            firstVal = 0;
            secondVal = 0;
            workMethod();
        } catch (InterruptedException x) {
            System.out.println("interrupted while in workMethod()");
        }
    }

    private void workMethod() throws InterruptedException {
        int val = 1;
        while (true) {
            stepOne(val);
            stepTwo(val);
            val++;
            Thread.sleep(200); // 再次循环钱休眠200毫秒
        }
    }

    // 赋值后,休眠300毫秒,从而使线程有机会在stepOne操作和stepTwo操作之间被挂起
    private void stepOne(int newVal) throws InterruptedException {
        firstVal = newVal;
        Thread.sleep(300); // 模拟长时间运行的情况
    }

    private void stepTwo(int newVal) {
        secondVal = newVal;
    }

    public static void main(String[] args) {
        MyThreadDemo dsr = new MyThreadDemo();
        Thread t = new Thread(dsr);
        t.start();

        // 休眠1秒,让其他线程有机会获得执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException x) {
        }
        for (int i = 0; i < 10; i++) {
            // 挂起线程
            t.suspend();
            System.out.println("dsr.areValuesEqual()=" + dsr.areValuesEqual());
            // 恢复线程
            t.resume();
            try {
                // 线程随机休眠0~2秒
                Thread.sleep((long) (Math.random() * 2000.0));
            } catch (InterruptedException x) {
                // 略
            }
        }
        System.exit(0); // 中断应用程序
    }

}

执行结果如下:

dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=false
dsr.areValuesEqual()=false
dsr.areValuesEqual()=false
dsr.areValuesEqual()=false
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=false

终止线程

反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。

终止线程的替代方法:同样是使用标志位,通过控制标志位来终止线程。