首页 > 代码库 > Java并发模型(一)

Java并发模型(一)

学习资料来自http://ifeve.com/java-concurrency-thread-directory/

一、多线程

  1. 进程和线程的区别: 一个程序运行至少一个进程,一个进程至少包含一个线程。
  2. 多线程: 多线程使得在一个程序内部能够拥有多个线程并行执行,一个线程的执行可以被认为是一个cpu在执行该程序,当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序。

多线程在同一个程序内部并发执行,因此会对相同的内存空间进行并发读写操作。

思考:

  1. 如果一个线程在读一个内存时,另一个线程正向该内存进行写操作,那么进行读操作的线程将获得什么结果呢?是写操作之前的旧值?还是写操作成功之后的新值?或者一半新一半旧的值?
  2. 如果两个线程同是写一个内存,在操作完成之后又是什么结果呢?是第一个线程写入的值?还是第二个线程写入的值?还是两个线程共同写入的一个混合值?

 

二、多线程的优点

1、资源利用率更好

           比如CPU在等待磁盘读取的时间非常空闲,可以利用这一段时间去做一些其他的事情。

2、程序设计在某些情况下更简单

           比如单线程去操作两个文件的读取和处理,需要记录每个文件的读取和处理状态。相反,如果用两个线程分别处理一个文件,一个线程在读取的时候就会阻塞另一个线程,此时,被阻塞的线程就可以去执行处理的操作。不但提升磁盘和CPU利用率,编程也更加容易实现。

3、程序响应更快

           例如服务器在某一个端口监听进来的请求,当一个请求到来时它去处理这个请求,处理完了再返回去监听其他请求。假设这个请求需要占用大量的时间呢?

           另一种方式是服务器监听线程把进来的请求传递给工作者线程,然后马上返回去监听,这样就能够接受和处理更多客户端发送过来的请求,服务的响应也更快了。

三、多线程的代价

         1、设计更复杂

         一般来说,多线程应用程序比单线程应用程序更加复杂,在多线程访问共享数据的时候,线程之间的交互往往非常复杂,不正确的线程同步产生的错误非常难以被发现,并且重现以修复。

         2、上下文切换的开销

         当CPU执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地数据、程序指针等,然后载入另一个线程的本地数据、程序指针等,最后才开始执行。这种切换叫做上下文切换(context switch),CPU会在一个上下文(context)中执行一个线程,然后切换到另一个上下文(context)执行另外一个线程。

         如果没有必要应该尽量减少上下文的切换。

         3、增加资源消耗

         除了CPU,线程还需要一些内存来维持它本地的堆栈,也需要占用操作系统中一些资源来管理线程。我们可以写一个程序创建100个线程,然后这些线程只是等待什么都不做,然后看看占用了多少内存。

四、并发编程模型

         并发编程模型指定了系统中的多线程如何通过协作来完成分配给它们的作业,不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同。

1、 并发模型与分布式系统之间的相似性

并发模理类似于分布式系统中使用的很多体系结构,在分布式系统中进程之间可以

通信(进程可能在不同的机器中),在并发系统中线程之间也可以相互通信。例如为工作者们(线程)分配作业的模型一般与分布式系统中的负载均衡系统比较相似。

2、 并行工作者

委托者将传入的作业被分配给不同的工作者上,每个工作者完成整个任务,工作者们运作在不同的线程上,甚至可能在不同的CPU上。

 

优点:容易理解,只需要添加更多的工作者来提高系统的并行度。

缺点:

  1. 并发工作者通常需要访问一些共享数据,无论是内存中还是数据库中,一旦涉及到共享状态情况就会变得复杂。线程需要以某种方式存取共享数据,以确保某个线程的修改能够对其他线程可见。线程需要避免竟态、死锁以及很多其他共享状态的并发问题。

等待访问共享数据时,线程之间的互相等待会丢失部分并行性,许多并发数据结构是阻塞的,意味着在某个时刻只有一个或者很少的线程能访问,导致共享数据出现竞争状态,出现一定串行化。

  1. 无状态的工作者: 共享状态能够被其他线程修改,所以工作者在每次需要的时候必须重读状态,以确保每次都能访问到最新的副本。工作者无法在内部保存这个状态称为无状态的。特别是状态保存在外部数据库中的时候,每次重读会导致速度变慢。
  2. 任务顺序是不确定的: 作业A可能在作业B之前就被分配工作者了,但是作业B反而有可能在作业A之前执行,也就是无法保证哪个作业最先或者最后被执行。

 

3、  流水线模型

也叫反应器系统、事件驱动系统或者无共享模型。

 

类似于工厂生产线上的工人们,每个工作者只负责作业中的部分工作,当完成了自己这部分工作就转发给下一个工作者,每个工作者在自己的线程中运行,并不会和其他工作者共享状态。

在实际中可能会有多个不同的虚拟流水线同时动作,现实可能是这样的情况。

 

Actors和Channels

Actors模型中每个工作者都被称为actor。Actor之间可以直接异步地发送和处理消息,actor可以用来实现一个或多个前面描述的那种作业处理流水线。

 

Channel模型中,工作都之间不直接进行通信。相返,它们在不同的通道中发布自己的消息(事件)。其他工作者可以在这些通道上监听消息,发送者无需知道谁在监听。

 

流水线模型的优点:

  1. 无需共享的状态
  2. 有状态的工作者
  3. 较好的硬件整合
  4. 合理的作业顺序

流水线模型的缺点:

  1. 作业执行往往分布到多个工作者上,并因此分布到项目中的多个类上。这样导致在追踪某个作业到底被什么代码执行时变得困难。
  2. 加大了代码编写的难度,有时工作者的代码需要写成回调处理的形式。若在代码中嵌入过多的回调处理,往往会出现所谓的回调地狱(callback hell)现像。

4、  函数式执行(Functional Parallelism)

函数式并行的基本思想是采用函数调用实现程序,函数被看作是代理人(agents)或者actor, 函数之间可以像流水线模型(AKA反应器或者事件驱动系统)那样互相发送消息。

         思考:使用哪种并发模型好呢?

         通常情况下,这个答案取决于你的系统打算做什么。如果你的作业本身就是并行的、独立的并且没有必要共享状态,你可能会使用并行工作者模型去实现你的系统。

         很多作业都不是自然并行和独立的,对于这种类型的系统,使用流水线并发模型能够更好的发挥它的优势,而且比并行工作者模型更有优势。

        

Java并发模型(一)