java特种兵读书笔记(5-8)——其他并发编程知识

shutdownhook


当发生System.exit(int)时,希望在系统退出前,执行一点任务来做一些资源方面的回收操作,shutdownhook可以达到这个目的。它利用hook的思路来实现,有时候把它叫做钩子。

假如在系统中通过Runtime.getRuntime().exec()或者new ProcessBuild()启动了子进程,该子进程一直在运行中,在当前进程退出时,子进程未必会退出,但此时业务上希望它退出,可以利用这一点。

Thread thread = new Thread(new Runnable() {
@Override public void run() {
while (true) {}}});
thread.setDaemon(true);
thread.start();
// 由于把子线程设置为守护线程,所以当main线程结束时,子线程自动结束,即exit方法执行了
// 这时候,会调用shutdownhook中我们自己定义的线程方法,来做一些资源回收
Runtime.getRuntime().addShutdownHook(
new Thread() {
public void run() {
System.out.println("garbage collecting...");}});

这里传入的参数是自定义的Thread线程对象,java进程调用exit时,会调用该线程的start方法将其运行起来,所以不要手工先启动。另外,这种回调线程不要设定为死循环程序,否则无法退出了

shutdownhook源码分析


lang包中有一个shutdown类,提供一个Runnable[] hooks数组,数组长度为10,即最多定义10个hook,提供add方法来注册新的hook对象。

首先通过RunTime的addShutdownHook方法,在ApplicationShutdownHooks中添加hook对象(我们自己注册的hook)。然后ApplicationShutdownHooks在static匿名块中,通过ShutDown的静态方法add方法注册了一个hook(内部的Runnable实现,和ShutDown关联起来),在run方法中调用了ApplicationShutdownHooks的runHooks。

当调用System.exit方法时,会间接调用Runtime.getRuntime().exit(),再调用sequence,然后调用runHooks方法,循环执行hook的run方法。这里的hook就是在ApplicationShutdownHooks匿名块中调用ShutDown的add方法添加的hook(系统内部的hook)。这个hook的run方法就是下面的run方法,

Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();}});

这里runHooks调用我们通过RunTime的addShutdownHook添加的hook(自己注册的hook)。

static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}

for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
try {
hook.join();
} catch (InterruptedException x) { }}}

这里runHooks采用每个hook采用每个任务单独有一个线程处理,而不是所有的任务在一个线程中串行处理。

Runtime.getRuntime().removeShutdownHook用于删除一个钩子线程。

Future



Future是一个接口,它有许多实现类,用的较多的是FutureTask。

FutureTask内部有一个sync属性,该属性所在的类是AQS的子类,它是基于AQS实现的。

FutureTask内部方法


①get()方法:用于获取任务结束的返回值。如果是Runnable则通常返回null,如果是Callable,则返回对应的return值。如果任务尚未结束,则当前线程会等待知道任务结束才返回。

②get(long, TimeUnit):可以设置一个超时时间,如果任务一直未结束,但是达到超时时间,会自动放弃等待,如果超时会抛出TimeoutException。

③isDone方法:用于判定任务是否结束,该方法不会阻塞,可以自行选择何时进行判定。返回true表示任务结束,这时调用get不会阻塞。

④cancel(boolean):尝试将任务的状态修改为CANCELLED,此时如果有线程调用get方法,由于任务尚未结束而等待。如果成功将状态修改为CANCELLED,会释放AQS锁。入参如果是true,则判定如果有正在运行的线程runner,会将其中断,该线程通常不是从FutureTask中获取数据的线程,而是运行任务的线程。

⑤isCanceled:如果状态已经为CANCELLED,那么该方法会返回true。

Future举例



ExecutorService的invokeAll方法,接收一个List<Callable>参数,返回一个List<Future>,这里的future调用get方法时不会阻塞,因为在invokeAll内部,已经帮我们做了这个逻辑。

for (Future<T> f : futures) {
if (!f.isDone()) {
try {
f.get(); // 在这个内部已经阻塞了
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}}}

也可以单个submit(Callable),然后返回单个future,然后get的时候阻塞。

异步与多线程


异步并不完全等价于多线程,多线程也并不等价于异步。

①异步(通知,回调):

某些任务可以将其交给一个单独的线程来完成,当前线程做其他的事情,最后以某种方式来通知。例如,将许多任务提交给线程池后,当前线程就无需关注线程池调度的细节了,对于使用者来说,这是异步的(比如mq发送过来了,接收方接收到之后,把处理mq消息的内容放在线程池中做,那么处理mq和接收mq就是异步的,两个不同的动作)。

但是异步不一定这样实现,它可以将这个动作交给Kernel去处理,因为大多数时候内核处理一些底层的等待问题会比应用程序更加直接。即使在没有通过内核做异步的情况下,内核也会与程序进程交互完成许多同步模型——内核始终会做许多事情,在某些场景下希望内核线程做更多的事情,通常在一些上下文无关的IO密集型场景中应用广泛。

②多线程

多线程自然不仅仅用于实现异步,也可以实现并行,并发,而异步只是一种设计方式,通常基于某种任务队列来实现。知识这个任务队列的抽象层次可能在程序内部(生产者消费者模型,生产者将任务放入队列,消费者不用关心,只需要去处理接下来的事情即可)。

异步并不完美,因为这样的程序设计不知道何时会返回数据。在代码中不断写回调方法,不断需要保存运行的上下文信息,会很头疼,出现问题也不好定位,性能上也未必完胜同步和阻塞,需要看场景。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值