线程中断

1.stop()方法

中断Java线程,首当其冲的当然是“方便的”stop()方法啦!直接调用,线程就会中止运行。 但是这个方法却在API中被标明为废弃,这是为什么? 这是正是因为它的“方便”导致的,想象某一个数据处理线程,数据处理到一半, 然后调用stop()方法,线程自然会被中断,但是他还有一半的数据没有处理! 这就破坏了数据的一致性,所以被废弃了。

public class Test {
    public static void main(String[] args) {

        User user = new User();
        user.setID(1);
        user.setName(String.valueOf(1));
        Thread readThread = new Thread(new ReadTask(user));
        readThread.start();
        try {
            while(true) {
                Thread writeThread = new Thread(new WriteTask(user));
                writeThread.start();
                Thread.sleep(150);
                writeThread.stop(); //a deprecated method
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
        }    
    }

    // ---------------
    // 内部类
    // ---------------
    private static class User {

        private long ID;
        private String name;

        public User() {

        }

        public long getID() {
            return ID;
        }

        public void setID(long iD) {
            ID = iD;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User [ID=" + ID + ", name=" + name + "]";
        }
    }

    private static class WriteTask implements Runnable {

        private User user;

        public WriteTask(User user) {
            this.user = user;
        }

        @Override
        public void run() {

            while(true) {
                long currentTimeMillis = System.currentTimeMillis();
                user.setID(currentTimeMillis);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException("the "+ThreadUtil.currentThreadName()+" has been interrupted");
                }
                user.setName(String.valueOf(currentTimeMillis));
            }
        }

    }

    private static class ReadTask implements Runnable {

        private User user;

        public ReadTask(User user) {
            this.user = user;
        }

        @Override
        public void run() {

            while(true) {
                if(user.getID()!=Long.parseLong(user.getName())) { //一旦出现数据不一致的情况就输出
                    System.out.println(user);
                }
                Thread.yield();
            }
        }
    }
}
/*****************************
控制台打印如下
...
User [ID=1489157102274, name=1489157102118]
User [ID=1489157102274, name=1489157102118]
User [ID=1489157102274, name=1489157102118]
User [ID=1489157102274, name=1489157102118]
User [ID=1489157102274, name=1489157102118]
...
******************************/

显然,上面这段代码,会在控制台打印大段信息。 WriteTask先setID,然后阻塞自身,如果此时调用了stop, 那么setName就不会调用,数据处理到一半被中断了,从而破坏数据的一致性。 当然,更可怕的是它的静默,这种错误没有任何异常信息,一旦出现问题很难定位,严重降低调试效率。 所以还是尽量不用stop()方法

2.中断位

利用stop()中断线程,其不会通知任何人说:“我要中断了!”,而是自顾自的中断自身。 这样,我们没法做善后处理。理想情况下,如果线程中断了, 要是能够通知我们:“线程中断了,快做一些善后处理!”,就好了。 这时,我们引入一个中断标志位,通过它来判断线程是否中断并做相应的处理。

public class Test {
    public static void main(String[] args) {

        User user = new User();
        user.setID(1);
        user.setName(String.valueOf(1));
        Thread readThread = new Thread(new ReadTask(user));
        readThread.start();
        WriteTask writeTask = new WriteTask(user);
        try {
            while(true) { //开启多个线程
                Thread writeThread = new Thread(writeTask);
                writeThread.start();
                Thread.sleep(150);
                writeTask.interrupt();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
        }    
    }
    //---------------
    // 内部类
    //---------------
    private static class User {

        private long ID;
        private String name;
        public User() {

        }
        public long getID() {
            return ID;
        }
        public void setID(long iD) {
            ID = iD;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "User [ID=" + ID + ", name=" + name + "]";
        }
    }

    private static class WriteTask implements Runnable {

        private boolean interrupt;
        private User user;
        public WriteTask(User user) {
            this.user = user;
        }
        @Override
        public void run() {

            while(true) {
                synchronized(user) {
                    if(interrupt) {
                        System.out.println("the "+Thread.currentThread().getName()+" has been interrupted");
                        break;
                    }
                    long currentTimeMillis = System.currentTimeMillis();
                    user.setID(currentTimeMillis);
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
                    }
                    user.setName(String.valueOf(currentTimeMillis));
                }
                Thread.yield();
            }
        }
        public void interrupt() {
            this.interrupt = true;
        }
    }

    private static class ReadTask implements Runnable {

        private User user;
        public ReadTask(User user) {
            this.user = user;
        }
        @Override
        public void run() {

            while(true) {
                synchronized(user) {
                    if(user.getID()!=Long.parseLong(user.getName())) { //一旦出现数据不一致的情况就输出
                        System.out.println(user);
                    }
                }
                Thread.yield();
            }
        }
    }
}
/*************************
控制台打印如下
...
the Thread-2 has been interrupted
the Thread-1 has been interrupted
the Thread-3 has been interrupted
the Thread-4 has been interrupted
...
***************************/

上面这段代码,ReadTask不会打印任何信息了,因为数据的一致性得到保证。 利用设置中断标志位interrupt,“温和的”告知线程:“要中断了,但怎么处理随你”。

3.interrupt()方法

interrupt()方法就是利用上述中断标志位的方式实现线程中断的。 他会调用一个native方法,设置线程的中断标志位。 另外,判断线程是否中断还有两个方法,静态方法interrupted(),实例方法isInterrupted()。 看下源码,JDK1.8的

  /**
     * 一个静态方法,清除当前中断标志
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * 实例方法,不会清除当前中断标志
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * 一个本地方法,返回线程中断情况
     * 而且当ClearInterrupted为true时,
     * 调用isInterrupted会清除当前线程的中断标志
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

我们试试isInterrupted()是如何工作的

public class Test {
    public static void main(String[] args) {
        Thread t = new Thread(new Task());
        t.start();
        t.interrupt(); //设置中断标志位
    }
    private static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.isInterrupted()); //返回当前中断情况,true;
            System.out.println(Thread.isInterrupted()); //由于isInterrupted()方法清除了中断标志位,所以返回false
        }
    }
}
/*********************
控制台打印如下
true
false
*********************/

4.中断标志位被清除的情况

当一个线程处于阻塞状态时(sleep、wait、await、join),线程本身就不在运行,如果这时调用了interrupt()方法,就会抛出异常。 而抛出异常时,线程的中断标志位会被清除。

public class Test {
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(new Task());
        t.start();
        Thread.sleep(2000);
        t.interrupt(); //设置中断标志位
    }
    private static class Task implements Runnable{
        @Override
        public void run() {

            try {
                Thread.sleep(5000);
            } catch(InterruptedException ex) {
                System.out.println(Thread.currentThread().interrupted()); //false
                //throw new RuntimeException(ex);
            }
        }
    }
}
/*******************
控制台打印如下
false
********************/

上面这段代码,在线程sleep时调用了interrupt,所以抛出了异常,异常的抛出清除了中断标志位 所以interrupted()返回false

总结

1.线程中断方式有stop()和interrupt()两种,其中stop()不安全已弃用

2.interrupt()、interrupted()、isInterrupted()之间的关系

引用

1.《实战Java高并发程序设计》

posted @ 2017-03-17 19:48  付大石  阅读(305)  评论(0编辑  收藏  举报