文档章节

《Java并发编程》第一章 — 简介 — 读书笔记

XuePeng77
 XuePeng77
发布于 2016/08/17 00:47
字数 3531
阅读 47
收藏 0
点赞 0
评论 0

1.1 并发简史

    资源利用率:在某些情况下,程序必须等待某个外部操作执行完成,例如输入操作或输出操作等,而在等待时程序无法执行其他任何工作。因此,如果在等待的同时可以运行另一个程序,那么无疑将提高资源的利用率。
    公平性:不同的用户和程序对于计算机上的资源有着同等的使用权。一种高效的运行方式是通过粗颗粒度的时间分片(Time Slicing)使这些用户的程序能共享计算机资源,而不是由一个程序从头运行到尾,然后再启用下一个程序。

    便利性:通常来说,在计算多个任务时,应该编写多个程序,每个程序执行一个任务并在必要时互相通信,这比只编写一个程序来计算所有任务更容易实现。

1.2 线程的优势

    如果使用得当,线程可以有效地降低程序的开发和维护成本,同时提升复杂应用程序的性能。线程能够将大部分的异步工作流转换成串行工作流,因此能更好地模拟人类的工作方式和交互方式。此外,线程还可以降低代码的复杂度,使代码更容易编写、阅读和维护。

    在GUI(Graphic User Interface,用户图形界面)应用程序中,线程可以提高用户界面的响应灵敏度,而在服务器应用程序中,可以提升资源利用率以及系统吞吐率。线程还可以简化JVM的实现,垃圾收集器通常在一个或多个专门的线程中运行。在许多重要的Java应用程序中,都在一定程度上用到了线程。

1.2.1 发挥多处理器的强大能力

    过去,多处理器系统的非常昂贵和稀少的。但现在,多处理器系统日益普及,并且价格也不断地降低,即使在低端服务器和中端桌面系统,通常也会采用多个处理器。

    由于基本的调度单位是线程,因此如果在系统中只有一个线程,那么最多同时只能在一个处理器上运行。在双处理器系统上,单线程的程序只能使用一半的CPU次元,而在拥有100个处理器的系统上,将有99%的资源无法使用。另一方面,多线程程序可以同时在多个处理器上执行。如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统的吞吐率。

    使用多个线程还有助于在单处理器系统上获得更高的吞吐率。如果程序是单线程的,那么当程序等待某个同步I/O操作完成时,处理器将处于空闲状态。而在多线程程序中,如果一个线程在等待I/O操作完成,另一个线程可以继续运行,使程序能够在I/O阻塞期间继续运行。

1.2.2 建模的简单性

    通常,当只需要执行一种类型的任务时,在时间管理方面比执行多种类型的任务要简单。当只有一种类型的任务需要完成时,只需要埋头工作,知道完成所有任务,你不需要花任何经理来琢磨下一步该做什么。

    例如Servlet和RMI(Remote Method Invocation,远程方法调用)。框架负责解决一些细节问题,例如请求管理、线程创建、负载平衡,并在正确的时刻将请求分发给正确的应用程序组件。编写Servlet的开发人员不需要了解有多少请求在同一时刻被处理,也不需要了解套接字的输入流或者输出流是否被阻塞。当调用Servlet的servlet方法来响应Web请求时,可以以同步方式来处理这个请求,就好像它是一个单线程程序。这种方式可以简化组件的开发,并缩短掌握这种框架的学习时间。

1.2.3 异步事件的简化处理

    服务器应用程序接受来自多个远程客户端的套接字连接请求时,如果为每个链接都分配其各自的线程并使用同步I/O,那么就会降低这类程序的开发难度。

    如果某个应用程序对套接字执行读取操作而此时还没有数据到来,那么这个读取将一直阻塞,直到有数据到达。在单线程程序中,这不仅意味着在处理请求的过程中将停顿,而且还意味着在这个线程被阻塞期间,对所有请求的处理都将停顿。为了避免这个问题,单线程服务器应用程序必须使用非阻塞I/O,这种I/O的复杂性要远远高于同步I/O,并且很容易出错。然而,如果每个请求都拥有自己的处理线程,那么在处理某个请求时发生的阻塞将不会影响其他请求的处理。

1.2.4 响应更灵敏的用户界面

    在现代GUI应用程序中,例如AWT和Swing等工具,都采用一个事件分发线程(Event Dispatch Thread, EDT)来代替主事件循环。当某个用户界面事件发生时,在事件线程中将调用应用程序的事件处理器。由于大多数GUI框架都是单线程子系统,因此到目前为止仍然在主事件循环,但它现在处理GUI工具的控制下并在其自己的线程中运行,而不是在应用程序的控制下。

1.3 线程带来的风险

    Java对线程的支持其实是一把双刃剑。虽然Java提供了相应的语言和库,以及一种明确的跨平台内存模型,这些工具简化了并发应用程序的开发,但同时也提高了对开发人员的技术要求,因为在更多的程序中会使用线程。

1.3.1 安全性问题

    线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程中操作执行顺序是不可预测的,甚至会产生奇怪的结果。

// 线程不安全
public class UnsafeSequence {
	private int value;
	
	// 返回一个唯一的数值
	public int getNext() {
		return value++;
	}
}

    在上面的代码中,UnsafeSequence类中将产生一个整数值序列,该序列中的每个值都是唯一的。在这个类中简要地说明了多个线程之间的交替操作将如何导致不可预料的结果。在单线程环境中,这个类能正确的工作,但在多线程环境中则不能。

    UnsafeSequence的问题在于,如果执行的时机不对,那么两个线程在调用getNext时会得到相同的值。虽然递增运算看上去是单个操作,但事实上它包含三个独立的操作:读取value,将value加1,并将计算结果写入value。由于运行时可能将多个线程之间的操作交替执行,因此这两个线程可能同时执行读取操作,从而使他们得到相同的值,并都将这个值加1。结果就是,在不同线程的调用中返回了相同的数值。

    UnsafeSequence类中说明的是一种常见的并发安全问题,成为竞态条件(Race Condition)。在多线程环境下,getValue是否会返回唯一的值,要取决于运行时对线程中操作的交替执行方式,这并不是我们希望看到的。

    由于多个线程要共享相同的内存地址空间,并且是并发运行,因此它们可能会访问或修改其它线程正在使用的变量。当然,这是一种极大的便利,因为这种方式比较其他线程间的通信机制更容易实现数据共享。但它同样也带来了巨大的风险:线程会由于无法预料的数据变化而发生错误。当多个线程同时访问和修改相同的变量时,将会在串行编程模型中引入非串行因素,而这种非串行性是很难分析的。要使多线程程序的行为可预测,必须对共享变量的访问操作进行协同,这样才不会在线程之间发生彼此干扰。

    通过将getNext修改为一个同步方法,可以修改UnsafeSequence中的错误:

// 线程安全
public class UnsafeSequence {
	private int value;
	
	// 返回一个唯一的数值
	public synchronized int getNext() {
		return value++;
	}
}

    如果没有同步,那么无论是编译器、硬件还是运行时,都可以随意安排操作的执行时间和顺序。虽然这些技术有助于实现更优秀的性能,并且通常也是值得采用的方法,但它们也为开发人员带来了负担,因为开发人员必须找出这些数据在哪些位置被多个线程共享,只有这样才能使这些优化措施不破坏线程安全性。

1.3.2 活跃性问题

    在开发代码时,一定要注意线程安全性是不可破坏的。安全性不仅对于多线程程序很重要,对于单线程程序同样重要。此外,线程还会导致一些在单线程程序中不会出现的问题,例如活跃性问题。

    安全性的含义是“永远不发生糟糕的事情”,而活跃性则关注与另一个目标,即“某件正确的事情最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成无限循环,从而使循环之后的代码无法得到执行。线程将带来一些其他的活跃性问题。包括死锁、饥饿、以及活锁。与大多数并发性错误一样,导致活跃性问题的错误同样是难以分析的,因为它们依赖于不同 线程的事件发生时序,因此在开发或者测试中并不总是能够重现。

1.3.3 性能问题

    与活跃性问题密切相关的是性能问题。活跃性意味着某件正确的事情最终会发生,但却不够好,因为我们通常希望正确的事情尽快发生。性能问题包括多个方面,例如服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高,或者可伸缩性较低等。与安全性和活跃性一样,在多线程程序中不仅存在于单线程程序相同的性能问题,而且还存在由于使用线程而引入的其他性能问题。

    在设计良好的并发应用程序中,线程能提升程序的性能,但无论如何,线程总会带来某种程度的运行时开销。在多线程程序中,当线程调度器挂起活跃线程并转而运行另外一个线程时,就会频繁的出现上下文切换操作(Context Switch),这种操作将带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是运行上。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存的总线的同步流量。所有这些因素都将带来额外的性能开销。

1.4 线程无处不在

    每个Java应用程序都会使用线程。当JVM启动时,它将为JVM的内部任务创建后台线程,并创建一个主线程来运行main方法。AWT和Swing的用户界面框架将创建线程来管理用户界面事件。Timer将创建线程来执行延迟任务。一些组件框架,例如Servlet和RMI,都会创建线程池并调用这些线程中的方法。

    如果要使用这些功能。那么就必须熟悉并发性和线程安全性,因为这些框架将创建线程并且在这些线程中调用程序中的代码。虽然将并发性认为是一种“可选的”或者“高级的”语言功能固然理想,但现实的情况是,几乎所有的Java应用程序都是多线程的,因此在使用这些框架时仍然需要对应用程序状态的访问进行协同。

    当某个框架在应用程序中引入并发性时,通常不可能将并发性仅局限于框架代码,因为框架本身会回调(Callback)应用程序的代码,而这些代码将访问应用程序的状态。同样,对线程安全性的需求也不能局限于被调用的代码,而是要延伸到需要访问这些代码所访问的程序状态的所有代码路径。因此,对线程安全性的需求将在程序中蔓延开来。

框架通过在框架线程中调用应用程序代码将并发性引入到程序中。在代码中将不可避免地访问应用程序状态,因此所有访问这些状态的代码路径都必须是线程安全的。

© 著作权归作者所有

共有 人打赏支持
XuePeng77
粉丝 39
博文 130
码字总数 174819
作品 0
丰台
Java程序员必读书单,家族又添新成员

点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 参与文末话题讨论,每日赠送异步图书。 ——异步小编 有些革命出其不意地吸引了全世界的眼球。Twitter、Linux操作系统和...

异步社区 ⋅ 05/09 ⋅ 0

语言学习读书笔记PHP和asp.net编程语言哪个更有前途?

编程语言一直是学习计算机编程门专业的热门讨论话题,而我也选择了这个专业,入学一年了,马上面临着语言的选择问题,业余我需要选择一门编程语言作为重点研究对象,那么问题来了:到底是选择...

原创小博客 ⋅ 05/30 ⋅ 0

怎样衡量两个字符串的相似度(编辑距离动态规划求解)

前言 目前计算句子相似性有很多不同的方案,比如基于语义词典的方法、基于相同词汇的方法、基于统计的方法和基于编辑距离的方法。这篇文章先介绍编辑距离的基础。 编辑距离 编辑距离其实就是...

超人汪小建 ⋅ 06/12 ⋅ 0

Java虚拟机标准(第10版)第一章(节选)翻译与评注

英文原文链接:https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-1.html 评注是括在鱼尾号之间的文字,其余均为翻译 Java虚拟机是Java平台的基石,这种技术实现了诸如跨平台、生成...

Jelif ⋅ 06/03 ⋅ 0

书单丨5本Java后端技术书指引你快速进阶

一名Java开发工程师 不仅要对Java语言及特性有深层次的理解 而且需要掌握与Java相关的 框架、生态及后端开发知识 本文涉及多种后端开发需要掌握的技能 对于帮助提高开发能力非常有帮助 NO.1...

Java高级架构 ⋅ 05/30 ⋅ 0

Java9后String的空间优化

前言 据我所知 Java 开发人员几乎任何时候都会想到 String,字符串确实已经成为最常用的类了,而且是大量使用。我们都知道,String 其实是封装了字符,里面必须由字符或字节数组来存放,从 ...

超人汪小建 ⋅ 05/19 ⋅ 0

怎样实现基于Trie树和字典的分词功能

前言 目前做分词比较流行的是用深度学习来做,比如用循环神经网络和条件随机场,也有直接用条件随机场或隐马尔科夫模型的。前面也实现过上面几种,效果挺不错,基于隐马尔科夫模型的差一点,...

超人汪小建 ⋅ 06/06 ⋅ 0

一图简看基于搜索的问答机器人设计

前言 对于 chatbot,现在学界更流行的实现方式是基于深度学习和强化学习,比如seq2seq模型,具体可参考前面的文章《深度学习的seq2seq模型》。 而对于工业界,直接用 seq2seq 模型来实现端对...

超人汪小建 ⋅ 05/22 ⋅ 0

使用zookeeper序列节点实现不可重入分布式锁

一、前言 在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候,juc包的锁就...

加多 ⋅ 01/12 ⋅ 0

计算机科学中抽象的好处与问题—伪共享实例分析

David John Wheeler有一句名言“计算机科学中的任何问题都可以通过加上一层间接层来解决”,一层不够就再加一层。后半句是我加的 (* ̄︶ ̄) ,虽然有点玩笑的意思,但是也的确能说明一些问题...

MageekChiu ⋅ 01/10 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring MVC基本概念

只写Controller

颖伙虫 ⋅ 13分钟前 ⋅ 0

微软重金收购GitHub的背后逻辑原来是这样的

全球最大的开发者社区GitHub网站花落谁家的问题已经敲定,微软最终以75亿美元迎娶了这位在外界看来无比“神秘”的小家碧玉。尽管此事已过去一些时日,但整个开发者世界,包括全球各地的开源社...

linux-tao ⋅ 14分钟前 ⋅ 0

磁盘管理—逻辑卷lvm

4.10-4.12 lvm 操作流程: 磁盘分区-->创建物理卷-->划分为卷组-->划分成逻辑卷-->格式化、挂载-->扩容。 磁盘分区 注: 创建分区时需要更改其文件类型为lvm(代码8e) 分区 3 已设置为 Linu...

弓正 ⋅ 34分钟前 ⋅ 0

Spring源码解析(六)——实例创建(上)

前言 经过前期所有的准备工作,Spring已经获取到需要创建实例的 beanName 和对应创建所需要信息 BeanDefinition,接下来就是实例创建的过程,由于该过程涉及到大量源码,所以将分为多个章节进...

MarvelCode ⋅ 54分钟前 ⋅ 0

js模拟栈和队列

栈和队列 栈:LIFO(先进后出)一种数据结构 队列:LILO(先进先出)一种数据结构 使用的js方法 1.push();可以接收任意数量的参数,把它们逐个推进队尾(数组末尾),并返回修改后的数组长度。 2....

LIAOJIN1 ⋅ 今天 ⋅ 0

180619-Yaml文件语法及读写小结

Yaml文件小结 Yaml文件有自己独立的语法,常用作配置文件使用,相比较于xml和json而言,减少很多不必要的标签或者括号,阅读也更加清晰简单;本篇主要介绍下YAML文件的基本语法,以及如何在J...

小灰灰Blog ⋅ 今天 ⋅ 0

IEC60870-5-104规约传送原因

1:周期循环2:背景扫描3:自发4:初始化5:请求6:激活7:激活确认8:停止激活9:停止激活确认10:激活结束11:远程命令引起的返送信息12:当地命令引起的返送信息13:文件传送20:响应总召...

始终初心 ⋅ 今天 ⋅ 0

【图文经典版】冒泡排序

1、可视化排序过程 对{ 6, 5, 3, 1, 8, 7, 2, 4 }进行冒泡排序的可视化动态过程如下 2、代码实现    public void contextLoads() {// 冒泡排序int[] a = { 6, 5, 3, 1, 8, 7, 2, ...

pocher ⋅ 今天 ⋅ 0

ORA-12537 TNS-12560 TNS-00530 ora-609解决

oracle 11g不能连接,卡住,ORA-12537 TNS-12560 TNS-00530 TNS-12502 tns-12505 ora-609 Windows Error: 54: Unknown error 解决方案。 今天折腾了一下午,为了查这个问题。。找了N多方案,...

lanybass ⋅ 今天 ⋅ 0

IDEA反向映射Mybatis

1.首先在pom文件的plugins中添加maven对mybatis-generator插件的支持 ` <!-- mybatis逆向工程 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-ma......

lichengyou20 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部