《精通Java并发编程(第2版)》读书笔记:并发设计原理

一、基本的并发概念

   1. 并发与并行

  关于并发,最被人们认可的定义是:在单个处理器上采用单核执行多个任务即为并发。在这种情况下,操作系统的任务调度程序会很快从一个任务切换到另一个任务,因此看起来所有任务都是同时运行的。对于并行来说也有同样的定义:同一时间在不同的计算机、处理器或处理器核心上同时运行多个任务,就是所谓的“并行”

  2. 同步

  在并发中,我们可以将同步定义为一种协调两个或更多任务以获得预期结果的机制。同步方式有两种。

    • 控制同步:例如,当一个任务的开始依赖于另一个任务结束时,第二个任务不能在第一个任务完成之前开始。
    • 数据访问同步:当两个或更多任务访问共享变量时,在任意时间里,只有一个任务可以访问该变量。

  与同步密切相关的一个概念是临界段。临界段是一段代码,由于它可以访问共享资源,因此在任何给定时间内,只能够被一个任务执行。互斥是用来保证这一要求的机制。

  并发系统中有着不同的同步机制。从理论角度来看,最流行的机制是:信号量(semaphore)、监视器

  线程安全:如果共享数据的所有用户都受到同步机制的保护,那么代码(或方法、对象)就是线程安全的。数据的非阻塞的CAS(compare-and-swap,比较和交换)原语是不可变的,这样就可以在并发应用程序中使用该代码而不会出任何问题。

  3. 不可变对象

  不可变对象是一种非常特殊的对象。在其初始化后,不能修改其可视状态(其属性值)。如果想修改一个不可变对象,那么你就必须创建一个新的对象。

  不可变对象的一个例子就是Java中的String类。当你给一个String对象赋新值时,会创建一个新的String对象。

  4. 原子操作和原子变量

  与应用程序的其他任务相比,原子操作是一种发生在瞬间的操作。在并发应用程序中,可以通过一个临界段来实现原子操作,以便对整个操作采用同步机制。

  原子变量是一种通过原子操作来设置和获取其值的变量。可以使用某种同步机制来实现一个原子变量,或者也可以使用CAS以无锁方式来实现一个原子变量,而这种方式并不需要任何同步机制。

  5. 共享内存与消息传递

  任务可以通过两种不同的方法来相互通信。第一种方法是共享内存,另一种同步机制是消息传递

 

二、并发应用程序中可能出现的问题

  1. 数据竞争

  如果有两个或多个任务在临界段之外对一个共享变量进行写入操作,也就是说没有使用任何同步机制,那么应用程序可能存在数据竞争(也叫作竞争条件)。

  在这些情况下,应用程序的最终结果可能取决于任务的执行顺序。请看下面的例子。

 1 package com.52test.java.concurrency;
 2 
 3 public class Account {
 4 
 5     private float balance;
 6 
 7     public void modify(float difference) {
 8         float value = this.balance;
 9         this.balance = value + difference;
10     }
11 
12 }

  假设有两个不同的任务执行了同一个Account对象中的modify()方法。由于任务中语句的执行顺序不同,最终结果也会有所不同。假设初始余额为1000,而且两个任务都调用了modify()方法并采用1000作为参数。最终结果应该是3000,但如果两个任务都在同一时间执行了第一条语句(即第8行代码),然后又在同一时间执行了第二条语句,那么最终结果将是2000。因为modify()方法不是原子的,而Account类也不是线程安全的。

  2. 死锁

  当两个(或多个)任务正在等待必须由另一个线程释放的某个资源,而该线程又正在等待必须由前述任务之一释放的另一个共享资源时,并发应用程序就出现了死锁当系统中同时出现如下四种条件时,就会导致死锁。我们将其称为Coffman条件

    • 互斥:死锁中设计的资源必须是不可共享的。一次只有一个任务可以使用该资源。
    • 占有并等待条件:一个任务在占有某一互斥的资源时又请求另一互斥的资源。
    • 不可剥夺:资源只能被那些持有它们的任务释放。
    • 循环等待:任务1正在等待任务2所占有的资源,而任务2又正在等待任务3所占有的资源,依次类推,最终任务n又在等待由任务1所占有的资源,这样就出现了循环等待。

 

posted @ 2019-07-26 13:13  程序员Wade  阅读(497)  评论(0编辑  收藏  举报