《Java并发编程实战》第一章笔记

简介

并发简史

  • 操作系统的出现使得计算机每次能运行多个程序,并且不同程序都在单独的进程中运行:操作系统为各个独立执行的进程分配各种资源,包括内存,文件句柄以及安全证书等。如果需要的话,在不同的进程之间可以通过一些粗粒度的通行机制来交换数据,包括:套接字信号处理器共享内存信号量以及文件等。
    • 资源利用率
    • 公平性
    • 便利性
  • 线程允许在同一个进程中同事存在多个程序控制流。线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器(Program Counter)、栈以及局部变量等。线程还提供了一种直观的分解模式来充分利用多处理器系统中的硬件并行性,而在同一个程序中的多个线程也可以被同时调度到多个CPU上运行。
  • 线程也被称为轻量级进程。在大多数现代操作系统中,都是以线程为基本的调度单位,而不是进程。如果没有明确的协同机制,那么线程将彼此独立执行。由于同一个进程中的所有线程都将共享进程的内存地址空间,因此这些线程都能访问相同的变量并在同一个堆上分配对象,这就需要实现一种比在进程间共享数据粒度更细的数据共享机制。如果没有明确的同步机制来协同对共享数据的访问,那么当一个线程正在使用某个变量时,另一个线程可能同时访问这个变量,这将造成不可预测的结果。

线程的优势

  • 线程可以有效地降低程序的开发和维护成本,同时提升复杂应用程序的性能。

发挥处理器的强大能力

  • 通过提高时钟频率来提升性能已变得越来越困难。
  • 处理器生产厂商都开始转而在单个芯片上放置多个处理器核。

建模的简单性

  • 如果在程序中只包含一种类型的任务,那么比包含多种不同类型任务的程序更易于编写,错误更少,也更容易测试。

异步事件的简化处理

响应更灵敏的用户界面

线程带来的风险

安全性问题

  • 线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程中的操作顺序是不可预测的,甚至会产生奇怪的结果。
@NotThreadSafe
public class UnsafeSequence {
	private int value;
	
	/** 返回一个唯一的数值 */
	public int getNext() {
		return value++;
	}
}

UnsafeSequence 的问题在于,如果执行时机不对,那么两个线程在调用getNext时会得到相同的值。虽然someVariable++看上去是单个操作,但事实上它包含三个独立的操作:读取value,将value加1,并将计算结果写入value。由于运行时可能将多个线程之间的操作交替执行,因此这两个线程可能同时执行读操作,从而使他们得到相同的值,并都将这个值加1.结果就是,在不同线程的调用中返回了相同的数值。
在这里插入图片描述
在UnsafeSequence类中说明的是一种常见的并发安全问题,称为竞态条件(Race Condition)。在多线程环境下,getValue是否会返回唯一的值,要取决于运行时对线程中操作的交替执行方式,这并不是我们希望看到的情况。

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

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

@ThreadSafe
public class Sequence {
	@GuardedBy("this") private int Value;
	
	public synchrnized int getNext () {
		return Value++;
	}
}

活跃性问题

安全性的含义是“永远不发生槽糕的事情”,而活跃性则关注于另一个目标,即“某件正确的事情最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的无限循环,从而使循环之后的代码无法得到执行。线程将带来其他一些活跃性向题。例如,如果线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永久地等待下去。第10章将介绍各种形式的活跃性问题以及如何避免这些问题,包括死锁(10.1节),饥饿(10.3.1节),以及活锁(10.33节)。与大多数并发性错误一样,导致活跃性问题的错误同样是难以分析的,因为它们依赖于不同线程的事件发生时序,因此在开发或者测试中并不总是能够重现。

性能问题

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

线程无处不在

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值