01、序章(多线程,多任务,并发的概念)

Java并发这一术语涵盖了java平台的多线程、并发和并行,它包括java并发工具,问题和解决办法。本教程涉及到java多线程相关的核心概念、并发结构、并发问题、多线程带来的好处和代价。

什么是多线程?

多线程是指在同一个程序里执行多个线程。线程就像是执行程序的一个单独的CPU,所以多线程程序就像是有多个CPU同时在执行不同部分的代码一样。

不过一个线程并不等同于一个CPU。通常情况下,多个线程共享一个CPU的执行时间。CPU在每个线程之间以一定间隔切换着执行。当然,程序的各个线程也可以在不同的CPU上运行。

为什么需要多线程?

为什么要在程序中使用多线程呢?原因有很多,常见的原因是:

**更好的使用单个CPU;

更好的使用多个CPU或者多核CPU;
从程序响应的角度给用户更好的体验;
从公平的角度给用户更好的体验。**

我们会在下面的章节里更详细的解释这些原因。

更好的使用单个CPU

使用多线程最常见的一个原因是为了能够更好的使用计算机资源。例如,如果某个线程通过网络发送了请求,在它等待网络返回结果的这段时间,另一个线程可以使用CPU来处理别的事情。此外,如果计算机配置了多个CPU或者多核CPU,那程序通过多线程就可以使用这些额外的CPU核心。

更好的使用多个CPU或者多核CPU

如果计算机配置了多个CPU或者多核CPU,那就需要在你的程序中使用多线程,因为这样才能用到所有的CPU或者CPU核心。一个单独的线程最多只能使用一个CPU。但是正如前文提到的那种情况,一个线程有时甚至没办法完全的利用好一个CPU。

从程序响应的角度给用户更好的体验

使用多线程的另一个原因是为了提供更好的用户体验。例如,假如你在某个用户界面点击了一个按钮,结果是它要发送一个网络请求。问题在于,该让哪个线程执行该请求呢?如果让更新用户界面的线程来处理,那在等待请求响应的这段时间,用户可能会察觉到界面“卡住”了。更好的做法是用一个后台线程来处理该请求,这样的话用户界面线程才有空闲来响应其他的用户请求。

从公平的角度给用户更好的体验

第四个原因是为了让用户更公平的分享计算机资源。例如,我们设想有一个服务器用于接收用户请求,但是这个服务器只有一个线程来处理这些请求。如果某个用户的请求需要花很长的时间来处理,那么所有其他用户的请求都要等这个请求处理完成才行。我们也可以让每个用户的请求都在自己的线程里执行,这样就避免了某个任务独占CPU的情况。

多线程VS多任务

在以前,计算机只有一个CPU,而且同一时间只能运行一个程序。由于多数较小的计算机不具备同时运行多个程序的能力,也就不存在多任务。当然了,相对于个人计算机,很多大型计算机很早就已经具备同时运行多个程序的能力了。

多任务

后来出现了多任务,也就是计算机可以同时运行多个程序(也就是任务或进程)。但这实际上并不是“同一时刻”,而是程序之间共享一个CPU。操作系统需要在程序之间切换,每个程序运行一小段时间,然后再切换到另一个。

多任务的出现给软件开发者带来了新的挑战。程序不能再像以前那样,想当然的以为可以占用所有的CPU时间,内存以及其他计算机资源。一个良好的程序应该把不用的资源释放掉,这样其他程序才能使用。

多线程

后来又出现了多线程,也就是同一个程序内部可以执行多个线程。我们可以把执行的线程当作执行该程序的一个CPU,所以同一个程序由多个线程执行就像是该程序由多个CPU执行一样。

多线程,有点难

对于某些类型的程序来说,用多线程来提升性能是非常好的方法。但是多线程比多任务的难度更大。由于多个线程在同一个程序里执行,因而它们会同时读取和写入同一块内存。这样的错误在单线程程序中是无法发现的。有些错误在只有单个CPU的机器上可能也发现不了,因为两个线程不可能真的“同时”执行。但是现代计算机出现了多核甚至多个CPU的配置。这就意味着这些单独的线程可以在各自的CPU或核心中同时执行。


如果一个线程读某一块内存的同时另一个线程在写这块内存,那第一个线程最后读到的值会是什么呢?是原来的值,还是第二个线程写入的值,还是两者混合之后的值呢?还有,要是两个线程同时写同一块内存,那在写完之后,这块内存的值会是什么,是头一个线程写的值,还是第二个线程写的值,还是这两者混合的值呢?

这种冲突如果不能很好的预防,任何结果都有可能。这甚至会导致程序的行为无法预测,因为每次的执行结果会不一样。因此,对于开发者而言,知道怎样采取正确的预防手段十分重要。开发者需要学习控制线程访问共享资源的方法,比如如何访问内存,文件以及数据库等。

这是本java并发教程要讲解的主题之一。

java的多线程和并发

java是最早的让开发者的多线程编程变得更简单的编程语言之一。java从一开始就已经具备了多线程的能力。因此,java开发者经常会遇到我们在前面描述过的问题。这是我写这篇java并发教程的原因。它既可以作为自己的笔记,也能让做java开发的同伴们参考受益。

本教程将主要侧重于java多线程,但是多线程中出现的一些问题,和多任务以及分布式系统中出现的问题很相似。因而本教程中也会参考多任务以及分布式系统。所以我没有用“多线程”,而是使用了“并发”这个词。

并发模型

最初的java并发模型假设同一个程序内执行的多个线程会共享对象。这种类型的并发模型通常被称为“共享状态并发模型”。有许多并发编程语言的结构和工具包都是根据这种并发模型来设计的。

然而,自第一本java并发的书问世以来,甚至自java5并发工具包公布以来,并发的架构和设计都已经发生了翻天覆地的变化。共享状态并发模型带来了大量的并发问题,并且这些问题很难巧妙的处理。因此,另一种并发模型流行了起来,它被称为“无共享”或者叫“分离状态并发模型”。在这一模型中,线程不会共享任何对象和数据。这样就避免了很多在“共享状态并发模型”中存在的并发访问问题。

而今已出现新的异步“分离状态”平台和工具包,比如Netty,Vert.x,Play / Akka和Qbit 。新的非阻塞式并发算法和非阻塞式工具也发布了,比如我们的工具包中新增了 LMax Disrupter。java7的Fork/Join框架还推出了新的并行函数式编程,java8又推出了collection streams 调用接口。

随着这些新的发展,又到了我更新java并发教程的时机了。因此这本教程又一次开始不断扩充。我会在空闲时间撰写并发布新的教程。