《七周七并发模型》阅读笔记(一)

一、线程与锁——第一天

线程与锁模型其实是对底层硬件运行过程的形式化,这种形式化既是该模型最大的优点,也是它最大的缺点。我们借助Java语言来学习线程与锁模型,不过内容也适用于其他语言。

1、知识点

线程与锁模型会带来三个主要的危害:竞态条件、死锁和内存可见性,本节提供了一些避免这些危害的准则:

  • 对共享变量的所有访问都需要同步化;(竞态条件
  • 读线程和写线程都需要同步化;(内存可见性
  • 按照约定的全局顺序来获取多把锁;(死锁
  • 当持有锁时尽量避免调用外星方法;(死锁
  • 应该尽可能缩短持有锁的时间;(死锁

2、自习

  • William Pugh的网站:Java内存模型
  • [http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html](JSR 133(Java内存模型)FAQ)
  • 深入理解Java内存模型-程晓明,这个系列的文章值得仔细研读
  • Java内存模型是如何保证对象初始化时线程安全的?是否必须通过加锁才能在线程之间安全地公开对象?
    (1)JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
    (2)新的 JMM 还寻求提供一种新的 初始化安全性 保证——只要对象是正确构造的(意即不会在构造函数完成之前发布对这个对象的引用,换句话说,不要让其他线程在其他地方能够看见一个构造期间的对象引用),然后所有线程都会看到在构造函数中设置的 final 字段的值,不管是否使用同步在线程之间传递这个引用。而且,所有可以通过正确构造的对象的 final 字段可及的变量,如用一个 final 字段引用的对象的 final 字段,也保证对其他线程是可见的。这意味着如果 final 字段包含,比如说对一个 LinkedList 的引用,除了引用的正确的值对于其他线程是可见的外,这个 LinkedList 在构造时的内容在不同步的情况下,对于其他线程也是可见的。
    (3)在讲了如上的这段之后,如果在一个线程构造了一个不可变对象之后(对象仅包含final字段),你希望保证这个对象被其他线程正确的查看,你仍然需要使用同步才行。
  • 了解反模式“双重检查锁模式”(double-checked locking)以及为什么称之为反模式。
    (1)程晓明的这篇文章——双重检查锁定与延迟初始化讲得十分清楚,关键在于:指令重排序导致在多线程情况下,其他线程可能访问到未初始化的对象。
    (2)解决方案有二:用volatile修饰instance对象;采用Initialization On Demand Holder idiom方案,即基于类的初始化方案(关键是JVM在初始化类的时候需要获取一把锁)。
    (3)选择方法:如果确实需要对实例字段使用线程安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。

二、线程与锁——第二天

内置锁虽然方便、灵活,但是也有很多限制:

  • 一个线程因为等待内置锁而进入阻塞后,就无法中断该线程了;
  • 尝试获取内置锁时,无法设置超时;
  • 获得内置锁,必须使用synchronized块;

Java 5之前,常常使用ReentrantLock锁代替synchronized关键字,因为ReentranLock锁可中断、可设置获取锁的超时时间、可实现细粒度加锁(链表上的交替锁)、可使用条件变量。

ReentrantLock的使用模式如下:

Lock lock = new ReentrantLock();
lock.lock();
try {
    《使用共享资源》
} finally {
      lock.unlock();
}

并发编程有时需要等待某个事件发生,条件变量就是为这种情况而生的。使用条件变量的模式是:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
      while(!《条件为真》) {
         condition.await();
      }
    《使用共享资源》
} finally {
      lock.unlock();
}

1、知识点

ReentrantLock和java.util.concurrent.atomic突破了使用内置锁的限制,利用新的工具我们可以做到:

  • 在线程持有锁的时候中断它;
  • 设置线程获取锁的超时时间;
  • 按照任意顺序获取和释放锁;
  • 用条件变量等待某个条件为真;
  • 使用原子变量避免使用锁。

2、自习

  • ReentrantLock创建时可以设置一个描述公平性的变量。什么是“公平”的锁?何时适合使用公平锁?使用非公平的锁会怎样?
    根据官方文档中的解释:
public ReentrantLock(boolean fair)
//Creates an instance of ReentrantLock with the given fairness policy.
//**Parameters:**
//fair - true if this lock should use a fair ordering policy

如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,也就是说等待时间最长的线程最有机会获取锁,也可以说锁的获取是有序的;反之,则是非公平锁。
公平锁的性能不如非公平锁——公平的获取锁没有考虑到操作系统对线程的调度因素,这样造成JVM对于等待中的线程调度次序和操作系统对线程的调度之间的不匹配;另一方面,公平锁可以防止“饥饿”情况的产生,在以TPS为唯一指标的场景下,可以考虑使用公平锁。

  • 什么是ReentrantReadWriteLock?它与ReentrantLock有什么区别?适用于什么场景?
    ReentrantReadWriteLock的中文名称是读写锁,在多线程场景中,如果没有写线程在操作模板对象,读写锁允许多个读线程同时读。当对于某个数据结构的操作主要是读操作而只有少量的写操作时,就非常适合使用ReentrantReadWriteLock。

  • 什么是“虚假唤醒”(spurious wakeup)?什么时候会发生虚假唤醒?为什么符合规范的代码不用担心虚假唤醒?
    (1)线程有可能在没有调用过notify()和notifyAll()的情况下醒来;
    (2)查看如下代码,doWait方法中发生了虚假唤醒——等待线程即使没有收到正确的信号,也能够执行后续的操作。

public class MyWaitNotify2{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      if(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}

(3)为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁(校注:这种做法要慎重,目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大)。被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。以下MyWaitNotify2的修改版本展示了这点:

public class MyWaitNotify3{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}
  • 什么是AtomicIntegerFieldUpdater?它与AtomicInteger有什么区别?适用于什么场景?
    (1)AtomicIntegerFieldUpdater用于保证已经new出来的实例的原子性,AtomicInteger用于构造具备原子性的Integer实例。
    (2)使用第三方库的时候,如果需要给第三方库提供的对象增加原子性,则使用AtomicIntegerFieldUpdater。

三、线程与锁——第三天

java.util.concurrent包不仅提供了第二天介绍的比内置锁更好的锁,还提供了一些通用高效、bug少的并发数据结构和工具。在实际使用中,较之自己实现解决方案,我们应更多地使用这些现成的工具。

1、知识点

  • 使用线程池,而不是直接创建线程
//线程池的大小设置为可用处理器数的2倍
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
while(true) {
  Socket socket = server.accept();
  executor.execute(new ConnectionHandler(socket));
}
  • 使用CopyOnWriteArrayList让监听器相关的代码更简单高效;
  • 使用ArrayBlockingQueue让生产者和消费者之间高效协作;
  • ConcurrentHashMap提供了更好的并发访问。

2、自习

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容