第21章节 并发

1. 定义任务

任务:任务就是一个执行线程要执行的一段代码。(在C语言中就是函数指针指向的某个地址开始的一段代码)

【记忆误区】:任务不是线程,线程是用来执行任务的。即任务由线程驱动。在顺序编程中,因为就只有一个执行线程,所有的程序代码都由该执行线程执行,所有的这些程序代码就可以看成一个任务。而并发编程可以使我们将原本的一个大的程序代码划分为多个分离的、独立的部分。这些独立部分(被称为子任务)中的每一个都可以由一个执行线程来执行。

在java中任务是由Runnable接口来描述的。我们具体要执行的任务可以写在run()方法中,以便交给某个线程去执行。

2. 线程

光定义了任务还不行,还得创建线程去执行它。也就是说得将某个任务和某个线程关联起来。

在创建Thread对象时,可以通过Thread的构造函数传递一个Runnable对象,从而将线程和任务关联起来。

【提示】:一定要重点区分线程和任务直接的区别!对于深刻理解并发编程,区分这两个概念很重要。如上所述,如果让多个线程对象和同一个任务对象关联起来,有可能会发生数据不一致。

3.Executor

客户端程序只需要提交任务(也就是要执行的代码块)就行,不需要考虑创建线程、将线程与任务关联、线程调度之类的细节了。从而提供了一种标准的方法将任务的提交过程与任务的执行过程解耦开来。客户提交的任务具体怎么执行,客户不用管,全部都由Executor做了。一方面客户不用关注任务的执行细节,另一方面,由于客户不需要关注任务执行细节了,客户可以安静地坐在那儿等任务执行的结果,当然客户也可以选择去干其他事情,一旦任务的执行结果出来了,我们就通知客户就好了。这样就引出了异步任务的概念。

Executor接口只有一个方法:void execute(Runnable command) ; // 在未来某个时间执行给定的命令

很明显Executor的execute方法提交的任务只能是那些没有返回值的任务,但有的任务是会有返回值的。ExecutorService接口继承并扩展了Executor接口,使得可以提交有返回值的任务。

具体的实现细节参考ThreadPoolExecutor类源码。

4. Runnable与Callable

两者都是用于描述任务的。Runnable描述的任务没有返回值,Callable用于有返回值的任务,Callable任务返回的计算结果保存在Future对象中。同时Runnable不会抛出一个受检查的异常,而Callable则可能抛出一个异常。

5. 后台线程

必须在线程启动前调用setDaemon()方法,才能把线程设置为后台线程。

后台线程在不执行finally子句的情况下就会终止其run()方法。

6. 捕获异常

如果任务的run方法中抛出了异常,就会向外传播的main中,在main中做try-catch是没有用的。解决方案,创建Thread的后调用它的setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法设置异常处理器。

如果想在代码中处处使用相同的异常处理器,可以在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获异常处理器。Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

7. 解决共享资源竞争

基本上所有的并发模式在解决线程冲突问题的时候,都是采用“序列化访问共享资源”的方案。意味着在给定时刻只允许一个任务访问共享资源。

8. 原子性

对于除了long和double之外的所有基本数据类型,JVM可以保证读写的原子性。对于long和double则无法保证,因为JVM可以将64位的读取和写入当做两个分离的32位操作来执行,在这之间有可能发生上下文切换,这被称为“字撕裂”。在定义long和double变量时,如果使用volatile关键字则可以保证读写的原子性。

[少数程序员大牛可以用原子操作代替同步,但只是少数!]

9. 可视性

在多处理器系统中,还有一个可视性的问题。一个任务做出的修改可能对其他任务是不可见的。(修改可能只是暂时性地存储在本地处理器的缓存中)。同步机制不但可以保证原子性,还有可视性的保证!

如果没有同步机制(这里指的是用原子性代替同步),那么修改时可视性将无法确定。volatile关键字还保证了修改的可视性!

[如果一个域可能会被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么就应该将这个域设置为volatile的。]

10. volatile

如果一个域的值依赖于它之前的值时(比如一个递增计数器),volatile就无法工作了。

如果某个域的值受到其他域的值的限制,volatile也无法工作![个人认为这里的无法工作指的是无法保证可视性。]

volatile唯一安全的情况是类中只有一个可变的域。

[最没有风险的方式就是使用synchronized关键字]

11. 原子类

AtomicInteger、AtomicLong、AtomicReference等类,它们主要用于java.uti.concurrent中的类,以及用于性能调用。

12. ThreadLocal类

防止任务在共享资源上产生冲突的第二种方式就是根除对变量的共享,线程本地存储可以为使用相同变量的每个不同的线程都创建不同的存储。

[上述的"相同变量"往往指的是Runnable类中的域。传递同一个Runnable实例给两个不同的Thread对象,那么这些线程在访问同一个Runnable实例的同一个域的时候每个线程都会保存该域的一个副本。所以Runnable中的需要ThreadLocal域直接用static修饰就好了,没必要定义成实例变量。]

ThreadLocal类包装的字段通常是static变量,否则如果是实例变量则完全没必要。

13.  中断

阻塞方法

当一个方法抛出 InterruptedException 时,它不仅告诉您它可以抛出一个特定的检查异常,而且还告诉您其他一些事情。例如,它告诉您它是一个阻塞(blocking)方法[即调用该方法会导致调用线程处于阻塞状态],如果您响应得当的话,它将尝试消除阻塞并尽早返回。

阻塞方法不同于一般的要运行较长时间的方法。一般方法的完成只取决于它所要做的事情,以及是否有足够多可用的计算资源(CPU 周期和内存)。而阻塞方法的完成还取决于一些外部的事件,例如计时器到期,I/O 完成,或者另一个线程的动作(释放一个锁,设置一个标志,或者将一个任务放在一个工作队列中)。一般方法在它们的工作做完后即可结束,而阻塞方法较难于预测,因为它们取决于外部事件。阻塞方法可能影响响应能力,因为难于预测它们何时会结束。

阻塞方法可能因为等不到所等的事件而无法终止,因此令阻塞方法可取消就非常有用。可取消操作是指能从外部使之在正常完成之前终止的操作。由 Thread 提供并受 Thread.sleep() 和 Object.wait() 支持的中断机制就是一种取消机制;它允许一个线程请求另一个线程停止它正在做的事情。当一个方法抛出 InterruptedException 时,它是在告诉您,如果执行该方法的线程被中断,它将尝试停止它正在做的事情而提前返回,并通过抛出 InterruptedException 表明它提前返回。 行为良好的阻塞库方法应该能对中断作出响应并抛出 InterruptedException,以便能够用于可取消活动中,而不至于影响响应。

线程中断

每个线程都有一个与之相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;当另一个线程通过调用 Thread.interrupt() 中断一个线程时,会出现以下两种情况之一。如果那个线程在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出 InterruptedException。否则, interrupt() 只是设置线程的中断状态。 在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取和清除。

中断是一种协作机制。当一个线程中断另一个线程时,被中断的线程不一定要立即停止正在做的事情。相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情。有些方法,例如 Thread.sleep(),很认真地对待这样的请求,但每个方法不是一定要对中断作出响应。对于中断请求,不阻塞但是仍然要花较长时间执行的方法可以轮询中断状态,并在被中断的时候提前返回。 您可以随意忽略中断请求,但是这样做的话会影响响应。

中断的协作特性所带来的一个好处是,它为安全地构造可取消活动提供更大的灵活性。我们很少希望一个活动立即停止;如果活动在正在进行更新的时候被取消,那么程序数据结构可能处于不一致状态。中断允许一个可取消活动来清理正在进行的工作,恢复不变量,通知其他活动它要被取消,然后才终止。

以上引自:《Java理论与实战:处理InterruptException》[IBM developerworks里面有很多高质量的文章,好好学习吧^_^,这里还有更多的java理论与实战的文章]

(1)如何给线程发中断消息?

如果有某个线程对象的引用,则直接调用Thread.interrupt();

executor.shutdownNow()会在executor启动的所有对象上调用interrupt()方法。

想中断executor启动的线程中的某一个线程该怎么办呢?通过executor.submit()方法提交任务,然后会获得一个Future对象,在Future对象上调用cancel(true)方法(参数值为true)即可。

(2)不是所有的阻塞都是可中断的

a. 试图执行IO操作的线程是不可中断的。比如InputStream的read()方法在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 在调用该read()方法的进程对象上调用interrupt()方法将不会响应中断。

b. 不能中断正在试图获取synchronized锁的线程。如果尝试着在一个对象上调用其synchronized方法,而这个对象的锁已经被其他任务获取了,那么调用任务将会被挂起(阻塞),直到这个锁可以获得。

小结:我们能够中断对sleep()的调用(或者任何会抛出InterruptException的调用)。但是不能中断正在试图获取synchronized锁或者试图执行IO操作的线程。 无论在任何时刻,只要任务以不可中断的方式被阻塞,那么都有潜在的会锁住程序的可能。

解决办法:

对于不可中断IO,可以关闭任务在其上发生阻塞的底层资源或者使用NIO(被阻塞的NIO通道会自动的响应中断);

对于试图获取synchronized锁的线程,可以使用ReentrantLock(在ReentrantLock上阻塞的任务具备可以被中断的能力)。

14. 线程之间的协作

之前处理的都是线程彼此之间的干涉问题,现在来考虑彼此之间的协作问题。

[Think In Java]基础拾遗4 - 并发的更多相关文章

  1. Java基础拾遗(二)

    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76358523冷血之心的博客) 马上就要秋招了,新的一轮笔试面试马上 ...

  2. Java基础拾遗(一)

    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76358391冷血之心的博客) 马上就要秋招了,新的一轮笔试面试马上 ...

  3. 图学java基础篇之并发

    概述 并发处理本身就是编程开发重点之一,同时内容也很繁杂,从底层指令处理到上层应用开发都要涉及,也是最容易出问题的地方.这块知识也是评价一个开发人员水平的重要指标,本人自认为现在也只是学其皮毛,因此本 ...

  4. Java基础-线程与并发1

    线程与并发 Thread 基本概念 程序: 一组计算机能识别和执行的指令 ,是静态的代码. 进程: 程序的一次运行活动, 运行中的程序 . 线程: 进程的组成部分,它代表了一条顺序的执行流. 进程线程 ...

  5. JAVA基础拾遗-论线程池的线程粒度划分与深浅放置

    摘要:多线程任务处理对提高性能很有帮助,在Java中提供的线程池也方便了对多线程任务的实现.使用它很简单,而如果进行了不正确的使用,那么代码将陷入一团乱麻.因此如何正确地使用它,如以下分享,这个技能你 ...

  6. Java基础加强之并发(四)synchronized关键字

    并发系列参考文章http://www.cnblogs.com/skywang12345/p/3323085.html#3907193 synchronized原理 在java中,每一个对象有且仅有一个 ...

  7. Java基础加强之并发(一)基本概念介绍

    基本概念介绍 进程:它是内存中的一段独立的空间,可以负责当前应用程序的运行.当前这个进程负责调度当前程序中的所有运行细节. 线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间. 进程是负 ...

  8. [Think In Java]基础拾遗3 - 容器、I/O、NIO、序列化

    目录 第十一章 持有对象第十七章 容器深入研究第十八章 Java I/O系统 第十一章 持有对象 1. java容器概览 java容器的两种主要类型(它们之间的主要区别在于容器中每个“槽”保存的元素个 ...

  9. [Think In Java]基础拾遗2 - 多态、反射、异常、字符串

    目录 第八章 多态第十四章 类型信息第十二章 通过异常处理错误第十三章 字符串 第八章 多态 1. 前期绑定 & 后期绑定 绑定是指将方法调用同一个方法主体关联起来的这么一个过程.如果在程序执 ...

随机推荐

  1. JQuery,C#,sqlServer 实现无极限多级树形控件

    最近好忙,好长时间没有更新博客了.......... 先看效果图: 此控件利用了 JQuery 插件: treeview google直接搜索就可以找到,这里就不提供链接了. 下载下来的压缩包包括了源 ...

  2. Linux忘记root密码怎么办?

    开篇前言:Linux系统的root账号是非常重要的一个账号,也是权限最大的一个账号,但是有时候忘了root密码怎么办?总不能重装系统吧,这个是下下策,其实Linux系统中,如果忘记了root账号密码, ...

  3. [20141121]无法通过powershell读取sql server性能计数器问题

    背景: 全新服务器,需要增加性能监控,发现无法通过powershell读取性能指标 解决方法: Open the Registry Editor by going to the Start Menu ...

  4. .NET并行编程实践(一:.NET并行计算基本介绍、并行循环使用模式)

    阅读目录: 1.开篇介绍 2.NET并行计算基本介绍 3.并行循环使用模式 3.1并行For循环 3.2并行ForEach循环 3.3并行LINQ(PLINQ) 1]开篇介绍 最近这几天在捣鼓并行计算 ...

  5. 【hive】——Hive sql语法详解

    Hive 是基于Hadoop 构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式来分析存储在Hadoop 分布式文件系统中的数据,可以将结构 化的数据文件映射为一张数据库表,并提供完整的SQL查 ...

  6. Windows Server 2012 虚拟化实战:存储(一)

    在计算机世界我们随处可以见的一种方法,那就是抽象.1946年冯诺依曼提出了计算机的基本结构包含:计算器,存储器和I/O设备.这可能是对计算机这一新生事物最重要的一次抽象,它直接影响了今后几十年计算机软 ...

  7. 萌新笔记——C++里创建 Trie字典树(中文词典)(一)(插入、遍历)

    萌新做词典第一篇,做得不好,还请指正,谢谢大佬! 写了一个词典,用到了Trie字典树. 写这个词典的目的,一个是为了压缩一些数据,另一个是为了尝试搜索提示,就像在谷歌搜索的时候,打出某个关键字,会提示 ...

  8. 详解Python中的循环语句的用法

    一.简介 Python的条件和循环语句,决定了程序的控制流程,体现结构的多样性.须重要理解,if.while.for以及与它们相搭配的 else. elif.break.continue和pass语句 ...

  9. Android 第一行代码

    ::-/stuapplication.pla.edu.cn.fragmentbestpractice W/dalvikvm﹕ VFY: unable to find class referenced ...

  10. POJ 2225 / ZOJ 1438 / UVA 1438 Asteroids --三维凸包,求多面体重心

    题意: 两个凸多面体,可以任意摆放,最多贴着,问他们重心的最短距离. 解法: 由于给出的是凸多面体,先构出两个三维凸包,再求其重心,求重心仿照求三角形重心的方式,然后再求两个多面体的重心到每个多面体的 ...