中断以及阻塞

文章讨论了Java中多线程的同步机制,包括在对象和方法上使用`synchronized`关键字,以及如何通过`ThreadLocal`实现线程局部存储以避免共享变量冲突。此外,还介绍了线程的中断机制,包括如何中断阻塞的任务,以及在`sleep`、`wait`和IO操作中如何处理中断。文章最后提到了资源管理的重要性,特别是在处理阻塞I/O时,关闭底层资源可以解除阻塞。
摘要由CSDN通过智能技术生成

在其他对象上同步
synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this),这正是PairManager2所使用的方式。在这种方式中,如果获得了synchronized块上的锁,那么该对象其他的synchronized方法和临界区就不能被调用量。因此,如果在this上同步,临界区的效果就会直接缩小在同步的范围内。
有时必须在另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步。

class DualSynch {
    private Object syncObject = new Object();

    public synchronized  void f() {
        for (int i = 0; i < 5; i++) {
            System.out.println("f()");
            Thread.yield();
        }
    }

    public  void g() {
        synchronized (syncObject) {
            for (int i = 0; i < 5; i++) {
                System.out.println("g()");
                Thread.yield();
            }
        }
    }
}

public class SyncObject {
    public static void main(String[] args) {
        final DualSynch dualSynch = new DualSynch();
        new Thread() {
            public void run() {
                dualSynch.f();
            }
        }.start();
        dualSynch.g();
    }
}

DaylSync.f()(通过同步整个方法)在this同步,而g()有一个在syncObject上同步的synchronized块。因此,这两个同步是互相独立的。通过在main中创建调用f的Thread对这一点进行演示,因为main线程是被用来调用g的。从输出中可以看到,这两个方式在同时运行,因此任何一个方法都没有因为对另一个方法的同步而阻塞。
线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同对的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。主要是,它们使得你可以将状态与线程关联起来。
创建和管理线程本地存储可以由java.long.ThreadLocal类来实现。

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Accessor implements Runnable {

    private final int id;

    public Accessor(int idn) {
        id = idn;
    }

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) { // 是否中断
            ThreadLocalVariableHolder.increment();
            System.out.println(this);
            Thread.yield();
        }
    }

    @Override
    public String toString() {
        return "#" +id + ": "+ ThreadLocalVariableHolder.get();
    }
}


public class ThreadLocalVariableHolder {
    /**
     * // ThreadLocal对象通常当作静态域存储。
     * 在创建ThreadLocal时,你只能通过get和set方法访问该对象的内容,
     * 其中,get方法将返回与其线程相关联的对象的副本,而set会将参数插入到
     * 为其线程存储的对象中,并返回存储中原有的对象。
     * increment和get方法在ThreadLocalVariableHolder中演示了这一点。
     * 注意:increment和get方法都不是synchronized的,因为ThreadLocal保证了不会出现不限竞争条件。
     * 当运行这个程序时,你可以看到每个单独的线程都分配了自己的存储,因为它们每个都需要跟踪自己的计数值,
     * 即便只有一个ThreadLocalVariableHolder对象
     */
    private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ 
        //线程变量,意思是ThreadLocal中填充的变量属于当前线程。
        // 该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。
        // ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
        private Random random = new Random();

        protected synchronized  Integer initialValue() {
            return random.nextInt(10000);
        }
    };

    public static void increment() {
        value.set(value.get()+1);
    }

    public static int get() {
        return value.get();
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            executorService.execute(new Accessor(i));
        }

        TimeUnit.SECONDS.sleep(3);

        executorService.shutdownNow(); //所有的Accessors 杀死
    }
}

终结任务
在前面的某些示例中,cancel和isCanceled方法被放到了一个所有任务都可以看到的类中。这些任务通过检查isCanceled来确定何时终止它们自己,对于这个问题来说,这是一种合理的方式。但是,在某些情况下,任务必须更加突然地终止。
装饰性花园
在这个仿真程序中,花园委员会希望了解每天通过多个大门进入公园的总人数。每个大门都有一个十字转门或某种其他形式的计数器,并且任何一个十字转门的计数值递增时,就表示公园中的总人数的共享计数值也会递增。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


class Count {
    private int count = 0;

    private Random rand = new Random(47);

    //删除synchronized关键字以查看计数失败
    // 如果移除synchronized ,就会有差异,只需要用互斥来同步对Count的访问。
    public synchronized int increment() {
        int temp = count;

        // 使用了Randon对象,目的是在从把count读取到temp中,到递增temp并将其存储到count的这段时间里,有大约一半的一半时间产生让步。
        // 如果synchronized关键字注释掉,那么这个程序就会崩溃,因为多个任务将同时访问并修改count

        if (rand.nextBoolean()) {
            Thread.yield();
        }

        return (count = ++temp);
    }

    public synchronized int value() {
        return count;
    }
}

/**
 * 单个Count对象来跟踪花园参观者的主计数值,并且将其Entrance类中的一个静态域进行存储。
 * Count.increment和Count.value都是synchronized的,用来控制对count域的访问。
 *
 * 每个Entrance任务都维护着一个本地值number,它包含通过某个特定入口进入的参观者的数量。
 * 这提供了对count对象的双重检查,以确保其记录的参观者数量是正确的。Entrance.run只是递增number和count对象,然后休眠100毫秒。
 *
 * 因为Entrance.canceled是一个volatile布尔标志,而它只会读取和赋值,所以不需要同步对其的访问,就可以安全地操作它,
 * 如果你对诸如此类的情况有任何疑虑,那么最好总是使用synchronized
 *
 */
class Entrance implements Runnable {

    private static Count count = new Count();

    private static List<Entrance> entries = new ArrayList<Entrance>();

    private int number = 0;

    //不需要同步来启动
    private final int id;

    private static volatile boolean canceled = false;

    public Entrance(int id) {
        this.id = id;
        //将此任务列在列表中.也可以防止死任务的垃圾收集
        entries.add(this);
    }

    public static void cancel() {
        canceled = true;
    }

    @Override
    public void run() {
        while (!canceled) {
            synchronized (this) {
                ++number;
            }
            System.out.println(this+" Total: " + count.increment());

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("sleep interrupted");
            }
        }
        System.out.println("Stopping "+ this);
    }

    public synchronized int getValue() {
        return number;
    }

    public String toString() {
        return "Entrance " + id + ": " + getValue();
    }

    public static int getTotalCount() {
        return count.value();
    }

    public static int sumEntrances() {
        int sum = 0;
        for (Entrance entry : entries) {
            sum += entry.getValue();
        }
        return sum;
    }
}

public class OrnamentalGarden {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            executorService.execute(new Entrance(i));
        }
        //跑一会儿。然后停下来收集数据
        TimeUnit.SECONDS.sleep(3);
        Entrance.cancel();
        executorService.shutdown();
        if (!executorService.awaitTermination(250, TimeUnit.MILLISECONDS)) { //等待任务结束,如果所有的任务在超时时间达到之前全部结束,则返回true 否则返回false。
                //表示不是所有所有的任务都已经结束了。尽管这会导致每个任务都退出其run方法,并因此作为任务而终止。
                // 但是Entrance对象仍旧是有效的,因为在构造器中,每个Entrance对象都存储在称为entrances的静态List<Entrance>中
                // 因此,sumEntrances仍旧可以作用于这些有效的Entrance对象。
            System.out.println("Some tasks were not terminated!");
        }

        System.out.println("Total: " + Entrance.getTotalCount());
        System.out.println("Sum of Entances: " + Entrance.sumEntrances());

    }
}

在阻塞时终结
前面示例中的Entrance.run在其循环中包含对sleep的调用。我们知道,sleep最终将唤醒,而任务也将返回循环的开始部分,去检查canceled标志,从而决定是否跳出循环。但是sleep一种情况,它使任务从执行状态变为阻塞状态,而有时你必须终止被阻塞的任务。
线程状态:
1:新建:当线程被创建时,它只会短暂的处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将这个线程转变为可运行状态或阻塞状态。
2:就绪:在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程,它就可以运行;这不同于死亡和阻塞状态。
3:阻塞:线程能够运行,但是有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。
4:死亡:处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run方法返回,但是任务的线程还可以被中断,你将要看到这一点。
进入阻塞状态:
一个任务进入阻塞状态,可能有如下原因:
1:通过调用sleep使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。
2:你通过调用wait使线程挂起。直到线程得到了notify或notifyAll消息或者signal或signalAll消息,线程才会进入就绪状态,我们将在稍后的小姐中验证这一点。
3:任务在等待某个输入输出完成。
4:任务视图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务以及获取了这个锁。
在较早的代码中,也可能会看到用suspend和resume来阻塞和唤醒线程,但是在现代java中这些方法被废止了(可能导致死锁)。stop方法也已经被废止了,因为它不释放线程获得的锁,并且如果线程处于不一致的状态,其他任务可以在这种状态下浏览并修改他们。
有时你希望能够终止处于阻塞状态的任务。如果对于处于阻塞状态的任务,你不能等待其到达代码中可以检查器状态值的某一点,因而决定让它主动地终止,那么你就必须强制这个任务跳出阻塞状态。
中断:
在Runnable.run方法的中间打断它,与等待该方法到达对cancel标志的测试等。当你打断被阻塞的任务时,可能需要清理资源。正因为这一点,在任务的run方法中间打断,更像是抛出的异常,因此在java线程中的这种类型的异常中断中用到了异常。为了在以这种方式终止任务时,返回众所周知的良好状态,你必须执行考虑代码的执行路径,并仔细编写catch子句以正确清除所有事物。
Thread类包含interrupt方法,因此你可以终止被阻塞的任务,这种方法将设置线程的中断状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException。当抛出该异常或者该任务调用Thread.interrupted时,中断将被复位。正如你将看到的,Thread.interrupted提供了离开run循环而不抛出异常的第二种方式。
为了调用interrupt,你必须持有Thread对象,你可能已经注意到了,新的concurrent类似乎在避免对Thread对象的直接操作,转而尽量通过Executor来执行所有操作,如果你在Executor上调用shutdowmNow,那么它将发送一个Interrupt调用给它启动的所有线程。这么做是有意义的,因为当你完成工程中的某个部分或者整个程序时,通常会希望同时关闭某个特定Executor的所有任务。然后,你有时也会希望只中断某个单一任务,如果使用Executor,那么通过调用submit而不是executor来启动任务,就可以持有该任务的上下文,submit将返回一个Future<?>,其中有一个未修饰的参数,因为你永远都不会在骑上调用get----持有这个Future的关键在于你可以在其上调用cancel,并因此可以使用它来中断某个特定任务。如果你将true传递给cancel,那么它就会拥有在该线程上调用interrupt以停止这个线程的权限。因此,cancel是一种中断由Executor启动的单个线程的方式。

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 下面的每个任务都表示了一种不同类型的阻塞。
 */
class SleepBlocked implements Runnable {

    // 是可以中断的阻塞示例
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(100); //第一个类是调用sleep
        } catch (InterruptedException e) {
            System.out.println("InterruptedException");
        }

        System.out.println("Exiting SleeepBlocked.run()");
    }
}

class IOBlocked implements Runnable {

    //不可中断的阻塞示例
    private InputStream inputStream;

    public IOBlocked(InputStream inputStream1) {
        inputStream = inputStream1;
    }

    @Override
    public void run() {
        try {
            System.out.println("Waiting for read()");
            inputStream.read(); // 第二个类调用read,
        } catch (IOException e) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted from blocked I/O");
            } else {
                throw new RuntimeException(e);
            }
        }
        System.out.println("Exiting IOBlocked.run()");
    }
}

class SynchronizedBlocked implements Runnable {

    //不可中断的阻塞示例
    // 这个程序证明IO在和synchronized块上的等待是不可中断的
    //无论是IO还是尝试调用synchronized方法,都不需要任何InterruptedException处理器
    //但是,为了演示synchronizedBlock,我们必须首先获取锁。
    public synchronized  void f() {
        while (true) { //从不释放锁
            Thread.yield();
        }
    }

    /**
     * 这个是通过构造器中创建匿名的Thread类
     * 的实例来实现的,这个匿名Thread类的对象通过调用f()获取了对象(这个线程必须有别于为SynchronizedBlock驱动run线程,因为一个线程
     * 可以多吃获得某个对象对象锁)。由于f()永远都不范湖,因此这个锁永远不会释放,而SynchronizedBlock.run在视图调用f,并阻塞以等待这个锁被释放。
     *
     */
    public SynchronizedBlocked() {

        new Thread() {
            public void run() { //该线程获取的锁
                f();
            }
        }.start();
    }

    @Override
    public void run() {
        System.out.println("Trying to call f()");
        f();
        System.out.println("Exiting SynchronizedBlocked.run()");
    }
}

public class Interrupting {

    private static ExecutorService executorService = Executors.newCachedThreadPool();

    static void test(Runnable r) throws InterruptedException {
        Future<?> submit = executorService.submit(r);
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("Interrupting " + r.getClass().getName());
        //System.out.println("Interrupting1 " + r.getClass());
        submit.cancel(true);// 中断由Executor启动的单个线程的方式
        System.out.println("Interrupt send to " +r.getClass().getName());
        //System.out.println("Interrupting send to1 " +r.getClass());
    }

    public static void main(String[] args) throws InterruptedException {
        test(new SleepBlocked());
        System.out.println("---------------------------");
        test(new IOBlocked(System.in));
        System.out.println("---------------------------");
        test(new SynchronizedBlocked());
        TimeUnit.SECONDS.sleep(3);
        System.out.println("Aborting with System.exit(0)");
        System.exit(0); //由于最近2次中断失败
    }
}

Interrupting SleepBlocked
Interrupt send to SleepBlocked
---------------------------
InterruptedException
Exiting SleeepBlocked.run()
Waiting for read()
Interrupting IOBlocked
Interrupt send to IOBlocked
---------------------------
Trying to call f()
Interrupting SynchronizedBlocked
Interrupt send to SynchronizedBlocked
Aborting with System.exit(0)

Process finished with exit code 0

从输出可以看到,你能够中断sleep的调用(或者任何要求抛出InterruptedException的调用)。但是,你不能中断正在试图获取synchronized锁或者试图执行IO操作线程。这有点令人烦恼,特别是在创建执行IO的任务时,因为这意味着IO具有锁住你的多线程程序的潜在可能。特别是对于基于Web的程序,这更是关乎厉害。
针对这类问题。有一个略显笨拙但是有时确实行之有效的解决方案,即关闭任务在其上发生阻塞的底层资源:

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CloseResource {
    public static void main(String[] args) throws IOException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ServerSocket server =  new ServerSocket(8080); //绑定端口
        InputStream socketInput =  new Socket("localhost",8080).getInputStream();
        executorService.execute(new IOBlocked(socketInput));
        executorService.execute(new IOBlocked(System.in));

        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("Shutting down all threads");

        executorService.shutdownNow();

        TimeUnit.SECONDS.sleep(1);

        System.out.println("Closing " + socketInput.getClass().getName());

        socketInput.close(); // Releases blocked thread

        TimeUnit.SECONDS.sleep(1);

        System.out.println("Closing "+ System.in.getClass().getName());

        System.in.close();

    }
}
Waiting for read()
Waiting for read()
Shutting down all threads
Closing java.net.SocketInputStream
Interrupted from blocked I/O
Exiting IOBlocked.run()
Closing java.io.BufferedInputStream
aaa
Interrupted from blocked I/O
Exiting IOBlocked.run()

Process finished with exit code 0

在shutdownNow被调用之后以及在两个输入流上调用close之前的延迟强调的是一旦底层资源被关闭,任务将解除阻塞,有一点很有趣,interrupt看起来发生在关闭Socket而不是关闭System.in的时刻。

幸运的是,在介绍了各种nio类提供了更人性化的IO中断,被阻塞的nio通道会自动地响应中断:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class NIOBlocked implements Runnable {

    private final SocketChannel socketChannel; //连接到TCP网络套接字的通道

    public NIOBlocked(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }

    @Override
    public void run() {
        try {
            System.out.println("Waiting for read() in "+ this);
            socketChannel.read(ByteBuffer.allocate(1)); //从堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器
        }catch (ClosedByInterruptException e) {
            System.out.println("ClosedByInterruptException");
        }catch (AsynchronousCloseException e) {
            System.out.println("AsynchronousCloseException");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        System.out.println("Exiting NIOBlocked.run() "+ this);
    }
}

public class NIOInterruption {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();

        ServerSocket serverSocket = new ServerSocket(8080);

        InetSocketAddress localhost = new InetSocketAddress("localhost", 8080); //连接ip和端口

        SocketChannel open = SocketChannel.open(localhost);
        SocketChannel open1 = SocketChannel.open(localhost);

        Future<?> submit = executorService.submit(new NIOBlocked(open));

        executorService.execute(new NIOBlocked(open1));

        executorService.shutdown();//平缓关闭,等待所有已添加到线程池中的任务执行完在关闭

        TimeUnit.SECONDS.sleep(1);
        //通过取消产生中断
        submit.cancel(true);

        TimeUnit.SECONDS.sleep(1);
        //通过关闭通道来释放阻塞
        open1.close();
    }
}
Waiting for read() in NIOBlocked@5b8e6da8
Waiting for read() in NIOBlocked@3b7c96ef
ClosedByInterruptException
Exiting NIOBlocked.run() NIOBlocked@5b8e6da8
AsynchronousCloseException
Exiting NIOBlocked.run() NIOBlocked@3b7c96ef

如你所见,你还可以关闭底层资源以释放锁,尽管这种做法一般不是所需的。注意,使用execute来启动两个任务,并调用e.shutdownNow将可以很容易地终止所有事物,而对于上面示例中的Future,只有在将中断发送给一个线程,同时不发送给另一个线程时才是必须的。

被互斥锁阻塞
就像在Interrupting.java中看到的,如果你尝试着在一个对象上调用synchronized方法,而这个对象的锁已经被其他任务获得,那么调用任务将被挂起(阻塞),直至这个锁可获得,下面的示例说明了同一个互斥可以如何能被同一个任务多次获得:

public class MultiLock {

    public synchronized void f1(int count) {
        if (count-- > 0) {
            System.out.println("f1() calling f1() with count " + count);
            f2(count);
        }
    }

    public synchronized void f2(int count) {
        if (count-- > 0) {
            System.out.println("f2() calling f1() with count " + count);
            f1(count);
        }
    }

    /**
     * 在main创建了一个调用f1的Thread,然后f1和f2互相调用直至count变为0.
     * 由于这个任务已经在第一个对f1的调用中获得了multiLock对象锁,因此同一个
     * 任务将在对f2的调用中再次获取这个锁,依次类推。这么做是有意义的,因为一个
     * 任务应该能够调用在同一个对象中的其他的synchronized方法,而这个任务已经持有锁了
     * @param args
     */
    public static void main(String[] args) {
        final MultiLock multiLock = new MultiLock();

        new Thread() {
            public void run() {
                multiLock.f1(10);
            }
        }.start();
    }
}

就像前面在不可中断的IO中所观察到的那样,无论在任何时刻,只要任务以不可中断的方式被阻塞,那么都有潜在的会锁住程序的可能,java se5 并发类库中添加了一个特性,即在ReentrantLock上阻塞的任务具备可以被中断的能力,这与在synchronized方法或临界区上阻塞的任务完全不同:

import jdk.nashorn.internal.ir.CallNode;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BlockedMutex {
    private Lock lock = new ReentrantLock();

    public BlockedMutex() {
        //立即获取它,以演示阻塞在ReentrantLock上的任务的中断
        lock.lock();
    }

    public void f(){
        try {
            /**
             * 当通过这个方法去获取锁时,如果其他线程正在等待获取锁,则这个线程能够响应中断,即
             * 中断线程的等待状态,
             * 可中断加锁,即在锁获取过程中不处理中断状态,而是直接抛出中断异常,由上层调用者处理中断。源码细微差别在于锁获取这部分代码
             */
            lock.lockInterruptibly(); //这将永远无法用于第二个任务
        } catch (InterruptedException e) {
            System.out.println("Interrupted from lock acquisition in f()");
        }
    }
}


class Blocked2 implements Runnable {

    BlockedMutex blocked = new BlockedMutex();

    @Override
    public void run() {
        System.out.println("Waiting for f() in BlockedMutex");
        blocked.f();
        System.out.println("Broken out of blocked call");
    }
}
public class Interrupting2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Blocked2());
        t.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Issuing t.interrupt()");
        /**
         *    1.当使用interrupt()方法去打断处于阻塞状态的线程时,会以异常的方式打印,而不会更新打断标记,因此,虽然被打断,但是打断标记依然为false。
         *    此时就需要在try catch异常捕获处再次调用interrupt()方法,就会使打断状态为true,优雅结束线程运行。
         *
         * 2.当使用interrupt()方法去打断正在运行线程时,被打断的线程会继续运行,但是该线程的打断标记会更新,更新为true,因此可以根据打断标记来作为判断条件使得线程停止。
         */
        t.interrupt();
    }
}
Waiting for f() in BlockedMutex
Issuing t.interrupt()
Interrupted from lock acquisition in f()
Broken out of blocked call

BlockedMutex类有一个构造器,它要获取锁创建对象上自身的Lock,并且并不释放这个锁。出于这个原因,如果你试图从第二个任务中调用f()(不同于创建这个BlockedMutex的任务),那么将会总是因Mutex不可获得而被阻塞。在Blocked2中,run方法总是调用blocked.f()的地方停止。当运行这个程序时,你将会看到,与IO调用不同,interrupt()可以打断被互斥锁阻塞的调用。

检查中断
注意,当你在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时(如你所见,除了不可中断的IO或被阻塞的synchronized方法之外,在其余的例外情况下,你无可事事)。但是如果根据程序运行的环境,你已经编写了可能会产生这种阻塞调用的代码,那又该怎么办呢?如果你只能通过在阻塞调用上抛出异常来退出,那么你就无法总是可以离开run循环。因此,如果你调用interrupt以停止某个任务,那么在run循环碰巧没有产生任何阻塞调用的情况下,你的任务将需要第二种方式来退出。
这种机会是由中断状态来表示的,其状态可以通过调用interrupt来设置。你可以通过调用interrupted来检查中断状态,这不仅可以告诉你interrupt是否被调用过,而且还可以清除中断状态。清除中断状态可以确保并发结构不会就某个任务被中断这个问题来通知你两次,你可以经由单一的InterruptedException或单一的成功的Thread.interrrupted测试来得到这种通知。如果想要再次检查以了解是否被中断,则你可以在调用Thread.interrupted时将结果存储起来。
下面的示例展示了典型的惯用法,你应该run方法中使用它来处理在中断状态被设置时,被阻塞和不被阻塞的各种可能:
import java.util.concurrent.TimeUnit;

/**

  • NeedsCleanup类强调在你经由异常离开循环时,正确清理资源的必要性。注意,所有在Blocked.run
  • 中创建的NeedCleanup资源都必须在其后面紧跟try-finally子句,以确保cleanup方法总是会被调用。
  • 你必须给程序提供一个命令参数,来表示在它调用interrupt之前以毫秒为单位的延迟时间。通过使用不同的延迟。
  • 你可以在不同地点退出Blocked3.run:在阻塞的sleep调用中,或者在非阻塞的数学
    */
class NeedsCleanup {
    private final int id;

    public NeedsCleanup(int ident) {
        id = ident;
        System.out.println("NeedsCleanup "+ id);
    }

    public void cleanup() {
        System.out.println("Cleaning up " + id);
    }
}

class Blocked3 implements Runnable {

    private volatile double d = 0.0;

    @Override
    public void run() {

        try {
            while (!Thread.interrupted()) {
                //point1
                NeedsCleanup needsCleanup = new NeedsCleanup(1);
                //在定义n1之后立即开始try-finally,以保证正确地清理n1
                try {
                    System.out.println("Sleeping");
                    TimeUnit.SECONDS.sleep(1);
                    NeedsCleanup needsCleanup1 = new NeedsCleanup(2);

                    try {
                        System.out.println("Calculating");

                        for (int i = 1; i < 2500000; i++) {
                            d = d+(Math.PI + Math.E)/d;
                        }

                        System.out.println("Finished time-consuming operation");
                    } finally {
                        needsCleanup1.cleanup();
                    }
                } finally {
                    needsCleanup.cleanup();
                }
            }
            System.out.println("Exiting via while() test");
        } catch (InterruptedException e) {
            System.out.println("Exiting via InterruptedException");
        }
    }
}
public class InterruptingIdim {
    public static void main(String[] args) throws InterruptedException {
        //if (args.length != 1) {
        //    System.out.println("usage: java InterruptingIdim delay-in-mS");
        //    System.exit(1);
        //}
        Thread thread = new Thread(new Blocked3());
        thread.start();
        TimeUnit.MILLISECONDS.sleep(new Integer(args[0]));
        thread.interrupt();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值