java多线程(核心篇)第三章

第三章

3.1 锁概述
  1. 锁的持有线程在其获得锁之后和释放锁之前这段时间内所执行的代码被称为临界区。
  2. Java平台中的锁包括内部锁和显示锁。内部锁是通过synchronized关键字实现的;显示锁是通过Lock接口的实现类实现的。
  3. 可见性的保障是通过写线程冲刷处理器缓存和读线程刷新处理器缓存这两个动作实现的。锁的获得隐含着刷新处理器缓存这个动作,这使得读线程在执行临界区代码前(获得锁之后)可以将写线程对共享变量所做的更新同步到该线程执行处理器的高速缓存中;而锁的释放隐含着冲刷处理器缓存这个动作。
  4. 锁对可见性、原子性和有序性的保障是有条件的:
    • 这些线程在访问同一组共享数据的时候必须使用同一个锁。
    • 这些线程中的任意一个线程,即使其仅仅是读取这组共享数据而没有对其进行更新的话,也需要在读取时持有相应的锁。
  5. 一个线程在其持有一个锁的时候能否再次(或者多次)申请该锁。如果一个线程持有一个锁的时候还能够继续成功申请该锁,那么我们就称该锁是可重入的。
  6. Java平台中锁的调度策略也包括公平策略和非公平策略,相应的锁就被称为公平锁和非公平锁。
  7. 一个锁实例所保护的共享数据的数量大小就被称为该锁的粒度。
3.2 内部锁:synchronized关键字
image.png
  1. synchronized 关键字修饰的代码块被称为同步块。
  2. 作为锁句柄的变量通常采用final修饰,锁不能改变。通常会使用private修饰作为锁句柄的变量。
  3. 同步静态方法相当于以当前类对象为引导锁的同步块。
  4. 公平锁保障锁调度的公平性往往是以增加了线程的暂停和唤醒的可能性,即增加了上下文切换为代价的。因此,公平锁适合于锁被持有的时间相对长或者线程申请锁的平均间隔时间相对长的情形。
  5. 使用显示锁的时候必须注意将锁的释放操作放在finally块中
  6. Lock接口定义了一个tryLock。该方法的作用是尝试申请相应Lock实例锁标识的锁。避免出现持有线程一直不释放锁这个锁(例如代码错误),同步在该锁之上的所有线程就会一直被暂停而使其任务无法进展。
image.png
  1. 改进型锁:读写锁
    • 读写锁允许多个线程可以同时读取(只读)共享变量,但是一次只允许一个线程对共享变量进行更新(包括读取后再更新)。
    • 任何一个线程持有一个读锁的时候,其他任何线程都无法获得相应锁的写锁。
    • 读写锁适用于
      • 只读操作比写(更新)操作要频繁得多。
      • 读线程持有锁的时间比较长。
3.3 轻量级同步机制:volatile关键字
  1. volatile关键字用于修饰共享可变变量,即没有使用final关键字修饰的实例变量或静态变量
  2. volatile变量不会被编译器分配到寄存器进行存储,对volatile变量的读写操作都是内存访问(访问高速缓存相当于主内存)操作。
  3. volatile关键字的作用包括:保障可见性、保障有序性和保障long/double型变量读写操作的原子性
  4. 一般而言,对volatile变量的赋值操作,其右边表达式中只要涉及共享变量(包括被赋值的volatiel变量本身),那么这个赋值操作就不是原子操作
  5. 如果被修饰的变量是个数组,那么volatile关键字只能够对数组引用本身的操作(读取数组引用和更新数组引用)起作用,而无法对数组元素的操作(读取、更新数组元素)起作用。
  6. volatile应用场景
    • 使用volatile变量作物状态标志。在该场景中,应用程序的某个状态由一个线程设置,其他线程会读取该状态并以该状态作为其计算的依据(或者仅仅读取并输出这个状态值)。此时使用volatile变量作为同步机制的好处是一个线程能够“通知”另外一个线程某种事件(例如,网络连接断连之后重新连上)的发生,而这些线程又无须因此而使用锁。从而避免了锁的开销以及相关问题。
    • 使用volatile保障可见性。其中一个线程更新了该变量之后,其他线程无须加锁的情况下也能够看到更新。
    • 使用volatile变量替代锁。利用volatile变量写操作具有的原子性,可以把一组可变状态变量封装成一个对象,那么对这些状态变量的更新操作就可以通过创建一个新的对象并将该对象引用赋值给相应的引用型变量来实现。volatile适合于多个线程共享一个状态变量(对象),而锁更实用用于多个线程共享一组状态变量。
    • 使用volatile实现简易版读写锁
      image.png
3.4 对象的发布与逸出
  1. 对象发布形式

    • 将对象引用存储到public变量中。
    • 在非private方法(包括public、protected、package方法)中返回一个对象。
    • 创建内部类,使得当前对象(this)能够被这个内部类使用。
    • 通过方法调用将对象传递给外部方法。
  2. 对象初始化安全:final与static

    2.1 static

    • java中类的初始化实际上也采取了延迟加载的技术,即一个类被java虚拟机加载之后,该类的所有静态变量的值仍然是其默认值(引用型变量的默认值为null,boolean变量的默认值为false),直到有个线程初次访问了该类的任意一个静态变量才使这个类被初始化——类的静态初始块("static{}")被执行,类的所有静态变量被赋予初始化。
    • static关键字仅仅保障读线程能够读取到相应字段的初始值,而不是相对新值。

    2.2 final

    • 当一个对象被发布到其他线程的时候,该对象的所有final字段(实例变量)都是初始化完毕的,即其他线程读取这些字段的时候所读取到的值都是相应字段的初始值(而不是默认值)。而非final字段没有这种保障,即这些线程读取该对象的非final字段时所读取到的值可能仍然是相应字段的默认值。
3.5 对象逸出
  1. 容易导致对象逸出的几种形式

    • 在构造器中将this赋值给一个共享变量。
    • 在构造器中将this作为方法参数传递给其他方法。
    • 在构造器中启动基于匿名类的线程。(其他线程可能看到的是一个未初始化完成的对象)
  2. 一般地,如果一个类需要创建自己的工作者线程,那么我们可以为该类定义一个init方法(可以是private的),相应的工作者线程可以在该方法或者该类的构造器创建,但是线程的启动则是在init方法中执行的。


    image.png
  3. 开销大小

    • 使用static关键字修饰引用该对象的变量。
    • 使用final关键字修饰引用该对象的变量。
    • 使用volatile关键字修饰引用该对象的变量。
    • 使用AtomicReference来引用该对象。
    • 对访问该对象的代码进行加锁。

你可能感兴趣的:(java多线程(核心篇)第三章)