zoukankan      html  css  js  c++  java
  • [读书笔记] 代码整洁之道(七): 并发编程

    第十三章 并发编程

    1. 为什么要并发

      并发是一种解耦策略。它帮我们把做什么(目的)和何时(时机)做分解。一般的单线程应用中,目的与时机紧密耦合,很多时候查看堆栈信息就能断定应用程序的状态。

      解耦目的与时机能明显地改进应用程序的吞吐量和结构

      迷思和误解:

      • 并发总能改变性能(X): 但只是在多个线程或处理器之间能分享大量等待时间的时候管用。
      • 编写并发程序无需修改设计(X):目的与时机的解耦往往对系统结构产生巨大影响。
      • 在采用Web或者EJB容器的时候,理解并发问题并不重要(X):最好了解容器在做什么,以及怎么解决并发更新、死锁等问题。

      一些中肯的说法:

      • 并发会在性能和编写额外代码上增加一些开销
      • 正确的并发是复杂的,即便对于简单的问题也是如此;
      • 并发缺陷并非总能重现,所以常被看做偶发事件而忽略;
      • 并发常常需要对设计策略的根本性修改
    2. 并发防御原则和技巧

      1) 单一权责原则SRP:认为方法、类、组件应当只有一个修改的理由。然而并发设计有时会相当复杂,以至于修改代码,所以建议分离并发相关代码与其他生产代码

      2) 推论:限制数据作用域:如果两个线程修改共享对象的同一个字段时,可能会互相干扰,导致未预期的行为。建议采用synchronized关键字在代码中保护一块使用共享对象的临界区。同时要谨记数据封装:严格限制对可能被共享的数据的访问

      1. 推论:使用数据复本:避免数据共享的好方法之一就是一开始就避免共享数据

        4) 推论:线程应尽可能的独立:每个线程都不与其他线程共享数据。每个线程处理一个客户端请求,从不共享的源头接纳所有请求数据,存储为本地变量。建议尝试将数据分解到可被独立线程(可能在不同处理器上)操作的独立子集
    3. 了解Java库

      使用Java 5以上版本编写线程时要注意:

      1) 使用类库提供的线程安全群集

      2) 使用executor框架执行无关任务

      3) 尽可能使用非锁解决方案

      4) 有几个类并不是线程安全的

      对于线程安全集群,需要掌握java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks等

    4. 了解执行模型

      一些基础定义:

      • 限定资源:并发环境中有着固定尺寸或数量的资源。比如数据库连接和固定尺寸读/写缓存等。
      • 互斥:每一时刻仅有一个线程能访问共享数据或共享资源。
      • 线程饥饿:一个或一组线程在很长时间内或永久被禁止。例如总让执行的快的线程先运行,加入执行的快的线程没完没了,则执行时间长的线程就会饥饿。
      • 死锁:两个或多个线程互相等待执行结束。每个线程都拥有其他线程需要的资源,得不到其他线程拥有的资源,就无法终止。
      • 活锁:执行次序一致的线程,每个都想要起步,但发现其他线程已经在路上。由于竞步的原因,线程会持续尝试起步,但在很长时间内都无法如愿,甚至永远无法启动。

      1) 生产者-消费者模型:一个或多个生产者线程创建某些工作,并置于缓存或队列中。一个或多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源

      2) 读者-作者模型:当存在一个主要为读者线程提供信息,但只是偶尔被作者线程更新的共享资源,吞吐量就会是问题。增加吞吐量,会导致线程饥饿或过时信息的积累。更新会影响吞吐量。

      3) 宴席哲学家:这种竞争式的问题会遭遇死锁、活锁、吞吐量、和效率降低等问题。

      建议学习及研究这些基础算法,理解解决方案

    5. 警惕同步方法之间的依赖

      Java的synchronized可以保护单个方法,但是如果在同一个共享类中有多个同步方法,系统就可能出错:

      建议:避免使用一个共享对象的多个方法

      但有时必须使用一个共享对象的多个方法,可以使用一下3种手段:

      1)基于客户端的锁定:客户端代码在调用第一个方法时,锁定服务器端,确保锁的范围覆盖了调用最后一个方法的代码。

      2)基于服务端的锁定:在服务端内创建锁定服务端的方法,调用所有方法,然后解锁,让客户端代码调用新方法。

      3)适配服务端:创建执行锁定的中间层。这是一种基于服务器端的锁定的例子,但不修改原始服务端的代码。

    6. 保持同步区域微小

      锁的代价很昂贵,一个锁维护的代码区域在任一时刻都只有一个线程执行,带来了延迟和额外开销。建议尽可能的减小同步区域

    7. 很难编写正确的关闭代码

      尽可能考虑关闭问题,今早令其工作正常。

    8. 测试线程代码

      当两个线程或多个线程使用同一段代码和共享数据,测试就比较复杂。

      建议:编写有潜力暴露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行

      精炼建议:

      • 将伪失败看做可能的线程问题:千万不能将系统错误归咎于偶发事件。
      • 先使非线程代码可工作:确保代码在线程之外可工作。
      • 编写可拔插的线程代码
      • 编写可调整的线程代码
      • 运行多于处理器数量的线程:任务交换越频繁,越有可能找到错过临界区或导致死锁的代码。
      • 在不同平台上运行
      • 调整代码并强迫错误发生:通过wait()、sleep()、yield()、priority()等调用。
  • 相关阅读:
    LinkedList -链表集合
    java包装类,自动装箱,拆箱,以及基本数据类型与字符串的转换
    StringBuilder -字符串缓冲区,节约内层空间变长数组
    System的两常用个静态方法
    StringBuilder
    mysql
    空房间
    数据结构占坑
    sql语句优化
    editPlus快捷键
  • 原文地址:https://www.cnblogs.com/nextStep/p/4844873.html
Copyright © 2011-2022 走看看