最近在看Java并发编程方面的东西,据说《Java并发编程实践》这本书不错,打算将读书心得记录下来,与大家一起分享。之前看豆瓣的评价,说中文版翻译的很差,所以下载了英文版,后来看机械工业出版社出版的这个翻译还好吧,就买了一本,大家可以自己决定哈~~
1.1并发简史
早期的计算机没有操作系统,都是一个程序运行到底再执行第二个程序,这对于计算机资源是很浪费的。后来出现了操作系统,通过引入进程的概念,同时可以执行多个任务,主要因为如下原因:
- 资源利用率:一个程序因为等待输入而暂停,另外的程序可以先使用CPU,提高利用率
- 公平性:所有的程序都应该公平地使用计算资源,不能由一个程序一直占着,等它用完了再给别的程序使用
- 便利性:在每个程序中编写一个任务,然后适当地通信交换结果,比将所有任务都写在一个程序中方便
线程的出现同样是基于以上考虑,线程也被称为轻量级进程。不同的是每个进程相当于一台虚拟的计算机,拥有自己的内存空间,而多个线程会共享进程范围内的资源,例如内存句柄和文件句柄等,但每个线程有各自的程序计数器(Programmer Counter)、栈以及局部变量。
1.2线程的优势
发挥多处理器的能力
现在提高单个CPU的主频已经越来越难,生产厂商开始转而生产更多核的CPU,现在连手机都有双核甚至四核的了。但是如果同时只有一个线程在执行,则多余的CPU会闲置,浪费了资源。
建模的简单性
将每个单独的任务放在一个线程中执行,然后在适当地时机进行交互,可以简化程序的开发。比如Servlet将每个用户请求单独放置在一个线程中进行响应,因此在service方法中不必考虑同时有多少请求需要处理。
异步事件的简化处理
如果只有一个线程,则如果某个I/O阻塞会影响整个程序的执行,因此往往采用非阻塞的I/O,但是这套机制复杂性较高。而如果创建多个线程,则一个线程的I/O阻塞并不会影响其他线程,整个程序还是可以运行下去。现代操作系统允许的线程数量得到了很大的提升,因此给每个客户端配置一个线程是可行的。
更灵敏的GUI响应
将用户的操作放置在一个事件线程中执行,可以避免程序执行一个耗时很长的操作导致程序“卡死”。
1.3线程带来的风险
安全性问题
在单线程中能安全执行(得到预期的结果)的程序在多线程下可能得到错误的结果。比如下面的代码
@NotThreadSafepublic class UnsafeSequence { private int value; /** Returns a unique value. */ public int getNext() { return value++; }}预期每次调用返回唯一的一个整数值,但是如果两个线程恰好按照某种特定的顺序执行,可能得到错误的结果,如下图
A、B两个线程得到的结果是一致的,因此这个程序在多线程环境下是不安全的。可以通过同步修复该错误,
@ThreadSafepublic class Sequence { @GuardedBy("this") private int nextValue; public synchronized int getNext() { return nextValue++; }}
活跃性问题
安全性指的是“永远不发生糟糕的事情”,而活跃性指的是“某件正确的事最终会发生”。在串行程序中,活跃性问题之一是无限循环,而并发程序中有死锁、饥饿以及活锁等。并发错误很难分析,因为bug并不是必现的。
性能问题
包括响应时间、吞吐率、资源消耗、可伸缩性等。
多线程程序中涉及到上下文切换(Context Switch),会有很大的开销。同时线程调度也会占用CPU。而为了获得线程安全,需要使用同步机制,而这抑制了编译器的某些优化。因此如果多线程实现不好,可能反而会降低程序的性能。
1.4线程无处不在
即使自己不直接使用多线程,但是使用的很多框架(比如Timer、Serlvet/JSP、RMI以及Swing和AWT等)也会使用,这会将并发引入到自己的程序中,因此也必须了解。
框架通过在框架线程中调用应用程序的代码将并发性引入到程序中。在该代码中如果访问某些应用程序状态,则所有访问这些状态的代码路径都必须是线程安全的。比如Servlet中service是多线程执行的,如果其中访问到了某个状态A,那么程序中的其他访问状态A的代码都必须相应地进行同步。