有时我们需要任务被突然的终止。这一节将学习有关终止的各类问题。

装饰性花园

这是一个让我们观察的示例,他不仅演示了终止问题,而且还演示了资源共享。下面的仿真程序中,花园委员会希望知道每天进入公园的总人数。每个公园门口都有一个计数器,并且任何一个门口的计数值递增时,就表示公园中的总人数的共享数值也会递增。

计数器:

public class Count {

private int count =0;

private Random random = new Random(47);

public synchronized int increment() {

int temp = count;

if (random.nextBoolean()) {

Thread.yield();

}

return (count = ++temp);

}

public synchronized int value() {

return count;

}

}

任务:

public class Entrance implements Runnable{

private static Count count = new Count();

private static List entrances = new ArrayList<>();

private int number = 0;

private final int id;

private static volatile boolean canceled = false;

public static void cancel() {

canceled = true;

}

protected Entrance(int id) {

super();

this.id = id;

entrances.add(this);

}

@Override

public void run() {

// TODO Auto-generated method stub

while (!canceled) {

synchronized (this) {

++number;

}

System.out.println(this + "Total" + count.increment());

try {

TimeUnit.MICROSECONDS.sleep(100);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

System.out.println("SLEEP INTERRUPTED");

}

}

System.out.println("结束了");

}

public synchronized int getValue() {

return number;

}

@Override

public String toString() {

// TODO Auto-generated method stub

return "Entrance" + id+": " + getValue();

}

public static int getTotalCount() {

return count.value();

}

public static int sumEntrances() {

int sum =0;

for (Entrance entrance : entrances) {

sum += entrance.getValue();

}

return sum;

}

}

测试类:

public class OrnamentalGraden {

public static void main(String[] args) throws Exception{

ExecutorService exec = Executors.newCachedThreadPool();

for (int i = 0; i < 5; i++) {

exec.execute(new Entrance(i));

}

TimeUnit.SECONDS.sleep(3);

Entrance.cancel();

exec.shutdown();

if (!exec.awaitTermination(250, TimeUnit.MICROSECONDS)) {

System.out.println("Som task were not terminated");

}

System.out.println("总人数" + Entrance.getTotalCount());

System.out.println("所有的 Entrances:" + Entrance.sumEntrances());

}

}

测试结果:

/....

Entrance0: 2092Total10425

Entrance1: 2082Total10426

Entrance3: 2082Total10427

Entrance2: 2085Total10428

Entrance4: 2088Total10429

结束了

结束了

结束了

结束了

结束了

总人数10429

所有的 Entrances:10429

我们使用单个的 count 对象来跟踪参观者的主计数器,并且将其作为 Entrance 的静态域存储。Count.increment() 和 Count.value() 都是 synchronized 的,用来控制对 count 域的访问。

每个 Entrance 任务都维护着一个本地值 number,它包含通过某个特定入口进入的参观者数量。这提供了双重检查,以确保记录的参观者数量是正确的。

因为 canceled 是 volatile 布尔值,而且它只会被读取和赋值,所以不需要同步对其访问,就可以安全的操作它。

执行任务 3 秒之后,main() 向 Entrance 发送 static cancel() 消息,然后调用 exec 对象的 shutdown() 方法,之后调用了 exec 的 awaitTermination() 方法。这个方法表示等待每个任务结束,如果所有的任务在超时时间未结束,则返回 ture,否则返回 false,表示不是所有的任务都已经结束。

当执行 run() 方法时,你会看到输出了通过每个门口的人数和此时的总人数。如果移除 Count.increment() 的 synchronized 的声明,你会看到总人数与你的期望会有差异,只要用互斥来同步对 Count 的访问,问题就可以解决。Count.increment() 通过使用 temp() 和 yield(),增加了失败的可能性。在真正的线程问题中失败的可能性在统计学角度看非常小,因此可能很容易的掉进陷阱。

在阻塞时中介

前面示例中的 run() 方法在其循环中包含对 sleep() 的调用。但是 sleep() 会使任务从执行状态变为阻塞状态,而有时你必须终止被阻塞的任务。

线程状态

一个线程可以处以一下四种状态:

新建 (new):当线程被创建时,他只会短暂的处以这种状态。此时已经分配了必要的资源,并执行初始化。此刻线程已经有资格获得 cpu 时间了,之后调度器会把这个线程转变为可运行状态或阻塞状态。

就绪 (Runnable):在这种状态下只要调度器把时间片分配给线程,线程就可以运行。在任意时刻线程可以运行也可以不运行,取决于调度去是否分配给线程时间片。

阻塞 (Blocked):线程能够运行,但有个条件组织它运行。当线程处于阻塞状态时,调度器将忽略线程不会分配给他任何 cpu 时间。

死亡 (Dead):处于死亡和终止状态的线程将不再可被调度,并且再也不会得到 cpu 时间,它的任务已经结束,或不再是可运行的。任务死亡的方式通常是从 run() 方法返回,但是任务的线程还可以被中断。

进入阻塞状态

一个任务进入阻塞状态可能有如下原因:

通过调用 sleep() 使任务进入休眠状态,在这种情况下任务在指定的时间内不会运行。

你通过调用 wait() 使线程挂起。直到线程得到了 notif() 或 notifall() 消息,线程才会进入就绪状态。

任务在等待某个输入或输出完成。

任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了锁。

stop() 方法已经被废弃,因为它不释放线程获得的锁,并且线程处于不一致的状态,其他任务在这种状态下可以浏览并修改他们。这所产生的问题很难发现。

现在我们需要的问题是:有时我们希望能够终止处于阻塞状态的任务。我们决定让其主动终止,那么必须强制这个任务跳出阻塞状态。

中断

在 Rannable.run() 方法中间打断任务是非常棘手的。当你打断被阻塞的任务时可能需要清理资源。正式由于这一点在 run() 方法中间打断更像是抛出异常。为了以此方式终止任务别切返回良好的状态你必须仔细考虑代码的执行路径,并正确编写 catch 子句正确清理所有的事物。

Thread 类包含 interrupt() 方法,因此你可以终止被阻塞的任务,这个方法将设置线程的中断状态。如果一个线程被阻塞或者试图执行一个阻塞操作,那么设置这个线程的阻塞状态将抛出 InterruptedException。当抛出该异常或者改任务调用 Thread.interrupt() 时,中断状态将被复位。Thread.interrupt() 提供了离开 run() 循环而不抛出异常的办法。

Java 的新类库尽量避免我们直接操作 Thread 对象,而是尽量使用 Executor 来执行所有操作。如果你在 Executor 上调用 shutdownNow() ,那么将会发送一个 interrupt() 给由它启动的所有线程。然而有时我们需要的是中断其中的某一个任务。此时如果我们使用 Executor 调用 submit() 来启动任务,就可以持有该任务的上下文对象。submit() 将返回一个泛型的 Future>,其中有一个未修饰的参数,持有这个 Future 的关键是可以在其上调用 cancel()。那么就可以拥有在改线程上调用 interrupt() 以停止这个线程的权限。

下面的示例用 Executor 展示了基本的 interrupt() 用法:

class SleepBlocked implements Runnable {

public void run() {

try {

TimeUnit.SECONDS.sleep(100);

} catch(InterruptedException e) {

print("InterruptedException");

}

print("Exiting SleepBlocked.run()");

}

}

class IOBlocked implements Runnable {

private InputStream in;

public IOBlocked(InputStream is) { in = is; }

public void run() {

try {

print("Waiting for read():");

in.read();

} catch(IOException e) {

if(Thread.currentThread().isInterrupted()) {

print("Interrupted from blocked I/O");

} else {

throw new RuntimeException(e);

}

}

print("Exiting IOBlocked.run()");

}

}

class SynchronizedBlocked implements Runnable {

public synchronized void f() {

while(true) // Never releases lock

Thread.yield();

}

public SynchronizedBlocked() {

new Thread() {

public void run() {

f(); // Lock acquired by this thread

}

}.start();

}

public void run() {

print("Trying to call f()");

f();

print("Exiting SynchronizedBlocked.run()");

}

}

public class Interrupting {

private static ExecutorService exec =

Executors.newCachedThreadPool();

static void test(Runnable r) throws InterruptedException{

Future> f = exec.submit(r);

TimeUnit.MILLISECONDS.sleep(100);

print("Interrupting " + r.getClass().getName());

f.cancel(true); // Interrupts if running

print("Interrupt sent to " + r.getClass().getName());

}

public static void main(String[] args) throws Exception {

test(new SleepBlocked());

test(new IOBlocked(System.in));

test(new SynchronizedBlocked());

TimeUnit.SECONDS.sleep(3);

print("Aborting with System.exit(0)");

System.exit(0); // ... since last 2 interrupts failed

}

}

执行结果:

Interrupting SleepBlocked

InterruptedException

Exiting SleepBlocked.run()

Interrupt sent to SleepBlocked

Waiting for read():

Interrupting IOBlocked

Interrupt sent to IOBlocked

Trying to call f()

Interrupting SynchronizedBlocked

Interrupt sent to SynchronizedBlocked

Aborting with System.exit(0)

上面的任务展示了三种不同的阻塞类型。SleepBlock 是可中断的阻塞类型,而 IOBlocked 和 SynchronizedBlocked 是不可中断的阻塞实例。从输出中可以看出,能够中断对 sleep() 的调用 (任何抛出 InterruptedException 的调用)。但是不能中断视图获取 synchronized 锁或者试图执行 I/O 操作的线程。

对于这类问题我们有一个行之有效的解决方案,即关闭任务在其发生阻塞的底层资源:

public class CloseResource {

public static void main(String[] args) throws Exception {

ExecutorService exec = Executors.newCachedThreadPool();

ServerSocket server = new ServerSocket(8080);

InputStream socketInput =

new Socket("localhost", 8080).getInputStream();

exec.execute(new IOBlocked(socketInput));

exec.execute(new IOBlocked(System.in));

TimeUnit.MILLISECONDS.sleep(100);

print("Shutting down all threads");

exec.shutdownNow();

TimeUnit.SECONDS.sleep(1);

print("Closing " + socketInput.getClass().getName());

socketInput.close(); // Releases blocked thread

TimeUnit.SECONDS.sleep(1);

print("Closing " + System.in.getClass().getName());

System.in.close(); // Releases blocked thread

}

}

执行结果:

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

Exiting IOBlocked.run()

在 shutdownNow() 被调用之后和两个输入流被调用 close() 之前的延迟强调的是一旦底层资源被关闭,任务将解除阻塞。

被互斥所阻塞

如果你尝试在一个对象上调用其 synchronized 方法,而这个对象的锁已经被其他任务获得,那么调用任务将会被挂起,直至这个锁被获得。下面的示例说明了同一个互斥可以被同一个任务多次获得:

public class MultiLock {

public synchronized void f1(int count) {

if (count-- >0) {

System.out.println("f1 调用 f2" + count);

f2(count);

}

}

public synchronized void f2(int count) {

if (count-- >0) {

System.out.println("f2 调用 f1" + count);

f1(count);

}

}

public static void main(String[] args) throws Exception{

final MultiLock multiLock = new MultiLock();

new Thread(){

public void run() {

multiLock.f1(5);

};

}.start();

}

}

执行结果:

f1 调用 f24

f2 调用 f13

f1 调用 f22

f2 调用 f11

f1 调用 f20

同一个任务先获得了 f1() 中的锁,又获得了 f2() 中的锁。这么做是有意义的,因为一个任务能够调用同一个对象中的其他的 synchronized 方法,而这个任务已经持有了锁。

前面 I/O 的例子我们观察到,只要任务以不可中断的方式被阻塞,那么都有潜在的锁住程序的可能。Java SE5 类库中添加了一个特性,即在 ReentrantLock 上阻塞的任务具备可以被中断的能力,:

加锁的对象:

public class BlockedMutex {

private Lock lock = new ReentrantLock();

public BlockedMutex() {

// TODO Auto-generated constructor stub

lock.lock();

}

public void f() {

// TODO Auto-generated method stub

try {

//可以被中断

lock.lockInterruptibly();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

System.out.println("BlockMutex 被中断异常");

}

}

}

任务:

public class Blocked2 implements Runnable{

BlockedMutex mutex = new BlockedMutex();

@Override

public void run() {

System.out.println("开始线程执行");

mutex.f();

System.out.println("被中断了");

}

}

线程启动任务,并中断:

public class Interrupting2 {

public static void main(String[] args) throws Exception{

// TODO Auto-generated method stub

Thread thread = new Thread(new Blocked2());

thread.start();

TimeUnit.SECONDS.sleep(1);

System.out.println("线程调用 interrupt(");

thread.interrupt();

}

}

执行结果:

开始线程执行

线程调用 interrupt(

BlockMutex 被中断异常

被中断了

检查中断

注意,当你在线程上调用 interrupt() 时,中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时。如果你的 run() 方法没有产生任何阻塞调用的情况下,调用 interrupt() 将无法停止某个任务。你的任务此时需要第二种方式退出。

这种机会是由中断状态来表示的,其状态可以通过调用 interrupt() 来设置。你可以调用 interrupted() 来检查中断状态,这不仅可以告诉你 interrupt() 是否可以被调用过,而且还可以清楚中断状态。清楚状态可以确保并发结构不会就某个任务被中断这个问题通知你两次,你可以经由单一的 InterruptedException 来得到通知。如果想再次检查了解是否被中断,则可以调用 Thread.interrupted() 时将结果保存起来。

下面展示了典型的管用方法:

public class NeedsCleanup {

private final int id;

protected NeedsCleanup(int id) {

super();

this.id = id;

System.out.println("NeedsCleanup:"+id);

}

public void cleanup() {

// TODO Auto-generated method stub

System.out.println("cleanup()"+id);

}

}

创建任务:

public class Blocked3 implements Runnable{

private volatile double d= 0.0;

@Override

public void run() {

while (!Thread.interrupted()) {

NeedsCleanup nCleanup = new NeedsCleanup(1);

try {

TimeUnit.SECONDS.sleep(1);

NeedsCleanup nCleanup2 = new NeedsCleanup(2);

try {

for (int i = 0; i < 2500000; i++) {

d = d+(Math.PI +Math.E)/d;

}

System.out.println("计算完毕");

} finally {

nCleanup2.cleanup();

}

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally {

nCleanup.cleanup();

}

}

}

}

创建测试类:

public class InterruptingIdiom {

public static void main(String[] args) throws Exception{

// TODO Auto-generated method stub

Thread thread = new Thread(new Blocked3());

thread.start();

TimeUnit.MICROSECONDS.sleep(3);

thread.interrupt();

}

}

测试结果:

NeedsCleanup:1

java.lang.InterruptedException: sleep interrupted

cleanup()1

NeedsCleanup:1

at java.lang.Thread.sleep(Native Method)

at java.lang.Thread.sleep(Thread.java:340)

at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

at concurrency.Blocked3.run(Blocked3.java:13)

at java.lang.Thread.run(Thread.java:745)

NeedsCleanup:2

计算完毕

cleanup()2

cleanup()1

NeedsCleanup:1

NeedsCleanup:2

/....

注意看第一条的异常,NeedsCleanup 类强调在你经由异常离开循环时,正确清理资源的必要性。注意:所有的 run() 方法中创建的资源都必须在其后紧跟 try-finally 子句,以确保 nCleanup() 方法总会被调用。

java终结方法_java编程思想之并发(终结任务)相关推荐

  1. java split()方法_Java编程性能优化一些事儿

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 Smile and stop complaining about the t ...

  2. java 吸血鬼数字_java编程思想之吸血鬼数字

    我觉得×××,所以我来了. -------------------------- 吸血鬼数字是指位数为偶数的数字,可以由一对数字相乘而得到,而这对数字各包含乘积的一半位数的数字,其中从最初的数字中选取 ...

  3. Java 第一阶段建立编程思想 【房屋出租系统】

    Java 第一阶段建立编程思想 [房屋出租系统] 1. 项目需求说明 2. 项目界面 1. 主菜单 2. 新增房源 3. 查找房源 4. 删除房源 5. 修改房源 6. 房屋列表 7. 退出系统 3. ...

  4. 在java中三种编程思想(OOA,OOD,OOP)

    在java中三种编程思想:OOA,OOD,OOP OOA 一.OOA的主要原则. 二.面向对象分析产生三种分析模型 三.OOA的主要优点 四.OOA方法的基本步骤 OOD 一.OOD背景知识 二.OO ...

  5. java编程思想 入门_java编程思想学习(基础)

    第一章 java介绍 1.编程的本质: 机器空间:解空间 问题空间:实际需要解决的业务问题,将该问题抽象化,在解空间中对问题建模. 编程就是建立问题空间和机器空间中的关联 面向对象编程思想: 1.万物 ...

  6. java编程思想书籍阅读_java编程思想-读书摘要(一)

    前言 程序设计其实是对复杂性的管理:待解决问题的复杂性,以及用来解决该问题的工具的复杂性. Java为程序员减少复杂度,减少开发健壮代码所需的时间以及困难.并着手解决各种复杂任务,例如(多线程和网络编 ...

  7. java编程思想 文献_JAVA编程思想英文参考文献和翻译

    JAVA编程思想英文参考文献和翻译 时间:2016-11-15 14:44来源:毕业论文 虽然java是基于C++基础上的,但是它更是纯粹的面向对象语 "If we spoke a diff ...

  8. java arrays方法_Java工具类Arrays中不得不知的常用方法

    原标题:Java工具类Arrays中不得不知的常用方法 Arrays 数组操作集数组转List ---asList 这个被"普遍"称为数组转List的方法,可能是Arrays内大家 ...

  9. JAVA面向接口的编程思想与具体实现

    面向对象设计里有一点大家已基本形成共识,就是面向接口编程,我想大多数人对这个是没有什么觉得需要怀疑的.         问题是在实际的项目开发中我们是怎么体现的呢? 难道就是每一个实现都提供一个接口就 ...

最新文章

  1. Sublime Text3配置Node.js开发环境
  2. 安装oracle11g client 【INS-30131】执行安装程序验证所需的初始设置失败的解决方法
  3. P1629邮递员送信与P1342请柬与P1821银牛派队研制联合胜利
  4. WF4.0实战(十一):邮件通知
  5. C++ 网络开发工具
  6. php获取访问者ip地址汇总,php获取访问者IP地址汇总_PHP
  7. JetBrains下载历史版本
  8. crm采用soap删除记录
  9. (1)JavaScript入门
  10. c51语言bit函数,keil C51中的本征函数库及使用说明
  11. 阿里云、腾讯云、UCloud 、华为云云主机对比测试报告
  12. matlab迭代算法实例_智能优化算法及其MATLAB实例-免疫算法笔记
  13. 拓端tecdat|R语言利用基线协变量提高随机对照试验的效率
  14. 简述RPL, DPL, CPL的区别与联系
  15. Linux之常用操作命令总结二
  16. Activiti6常见错误汇总
  17. 《电磁场与电磁波》课程笔记(一)——矢量与坐标系
  18. DirectX 9.0 (5) 点光源
  19. 【转】关于ATSC与DVB的比较
  20. 数学知识-三角函数公式大全(值得收藏)

热门文章

  1. 一文详解 Java 的八大基本类型!
  2. 我国研发5款自主产权3D显卡;哈啰单车回应异常;Jboot 2.2.4发布 | 极客头条
  3. Windows 3.1 往事:历史上第一个真正占据主导地位的操作系统
  4. 京东扩招 1.5 万员工;程维卸任快的打车法人;库克纪念乔布斯 64 岁诞辰 | 极客头条...
  5. Linux 常用命令如何使用?
  6. 陌陌 3 千万数据暗网出售;美团反腐 89 人受刑事查处;iPhone 推迟 5G 采用时间 | 极客头条...
  7. 1 亿人民币的差距!硅谷初创公司工资期权调查报告里的 0.1% 与 1%
  8. 【Linux 4,2021最新Java笔试题及答案
  9. 字节跳动架构师讲解Java开发!dockerstop命令
  10. c++ 获取当前时间_ThinkPHP6中获取参数的3种常用方法【总结】