Java多线程编程实战指南(核心篇)读书笔记(五)

时间:2022-03-19 21:23:58

(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76730459冷血之心的博客)

博主准备恶补一番Java高并发编程相关知识,接下来将阅读该书,并且进行比较详细的总结,好记性不如烂笔头,加油。

Java多线程编程实战指南(核心篇)读书笔记(五),主要记录该书第9章和第11/12章的基本概念等知识,欢迎关注本博客。

  1. Java异步编程
    1. 同步计算与异步计算
      1. 同步和异步的区别:
        1. 同步存在等待,异步执行不存在等待
        2. eg. A叫B去吃饭吧,A等着和B一起去,就是同步;A说完我们去吃饭吧,自己先走了,这就是异步
      2. 同步往往意味着阻塞;异步则往往意味着非阻塞
      3. 理解下同步异步和阻塞非阻塞的关系:
        1. 同步阻塞:
          1. 首先执行任务的方式是同步的,其次阻塞意味着同步任务执行结束前,该任务的发起线程并没有在运行(生命周期状态不为Runnable)
        2. 同步非阻塞:
          1. 首先执行任务的方式是同步的,其次非阻塞意味着同步任务执行结束前,该任务的发起线程仍然在运行,只不过此时该线程的主要动作是检查相应的任务是否执行结束(通过轮询的检查方式)。
        3. 异步阻塞和异步非阻塞同样的道理
        4. 总结:同步和异步指的是线程执行任务的方式;阻塞非阻塞指的是任务的发起线程是否仍然在运行(轮询方式)
      4. 同步和异步的优缺点:
        1. 同步代码简单、直观,但是往往意味着阻塞,限制了系统的吞吐率
        2. 异步有利于提高系统吞吐率,但是需要更为复杂的代码和更多的资源投入
    2. Java Executor框架
      1. 基本概念
        1. Runnable接口和Callable接口都是对任务处理逻辑的抽象
        2. Executor接口则是对任务执行进行的抽象
        3. 使用该框架的好处就是可以解耦任务的提交和任务的具体执行细节
          1. 将任务提交给Executor接口,则执行方式为同步执行
          2. 将任务提交给ThreadPoolExecutor接口,则为异步执行
        4. Executor接口一定程度上缩小了同步编程和异步编程的代码编写方式
        5. 由于Executor接口的局限性,出现了其子接口ExecutorService接口
          1. 可以返回相应的Future执行结果
          2. shutdown和shutdownNow方法
          3. ThreadPoolExecutor是ExecutorService的默认实现类
      2. 实用工具类Executors
        1. 就像Array和Arrays;Collection和Collections的关系一样;带s的为工具类,定义了一堆工具方法
        2. 定义了返回线程池的方法:
          1. newCachedThreadPool()
          2. newFixedThreadPool()
          3. newSingleThreadExecutor()
      3. 异步任务的批量执行:CompletionService
        1. Future接口能够方便地获取异步任务的处理结果
        2. CompletionService则可以实现一次性提交一批异步任务并获取这些任务的处理结果
      4. 计划任务:Scheduled Task
        1. 有些情况下,我们需要事先提交一个任务,这个任务并不是立即被执行的,而是需要在指定的时间或者周期性地被执行
        2. 典型的计划任务有:清理系统的来及数据、系统监控和数据备份等。
        3. ScheduledExecutorService接口是ExecutorService的子接口
        4. ScheduledExecutorService接口的默认实现类是ScheduledThreadPoolExecutor
        5. ScheduledThreadPoolExecutor类是ThreadPoolExecutor的子类
  2. 多线程编程的硬件基础与Java内存模型
    1. 填补处理器与内存之间的鸿沟:高速缓存
      1. 由来:
        1. 现代处理器处理能力提升飞快,内存DRAM访问速率提升有限
        2. 主内存执行一次内存读、写操作所需的时间,可能足够处理器执行上百条的指令
        3. 为了弥补差距,硬件设计者在主内存和处理器之间引入了高速缓存(Cache)
      2. 高速缓存是一种存取速率远比主内存大容量远比主内存小的存储部件,每个处理器都有其高速缓存。
    2. Java同步机制与内存屏障
      1. 内存屏障:对一类指令的称呼,该指令可以禁止重排序
      2. volatile关键字的实现:
        1. 有序性的保证机制:
          1. Java虚拟机(JIT编译器)在volatile变量读操作之后插入一个获取屏障,保证这个volatile变量读操作先于该屏障之后的任何读写操作被提交
          2. 在volatile变量写操作之前插入一个释放屏障,保证该屏障之前的任何读写操作都先于这个volatile变量的写操作被提交
      3. synchronized关键字的实现:
        1. synchronized对原子性的保证:
          1. JIT编译器会在monitorenter(用于申请锁的字节码指令)对应的指令后临界区开始前的地方插入一个获取屏障。
          2. Java虚拟机会在临界区结束后monitorexit(用于释放锁的字节码指令)对应的指令前的地方插入一个释放屏障
          3. 获取屏障和释放屏障一起保证了临界区内的任何读写操作都无法被重排序到临界区之外,再加上锁的排他性,使得临界区内的操作具有原子性
        2. 对有序性的保证:
          1. 同volatile机制
      4. 内存屏障的缺点:
        1. 内存屏障可以禁止指令重排序,代价是会阻止编译器、处理器做一些性能优化
        2. 内存屏障还会导致冲刷缓存器和清空无效队列,比较耗时
    3. Java内存模型:JMM(Java Memory Model)
      1. 内存模型,从以下三个方面开解答线程安全问题:
        1. 原子性问题
          1. 针对实例变量、静态变量的读写操作,哪些是具备原子性的,哪些可能不具备原子性?
        2. 可见性问题
          1. 一个线程对实例变量和静态变量进行的更新在什么情况下能够被其它线程所读取?
        3. 有序性问题
          1. 一个线程对多个实例变量、静态变量进行的更新在什么情况下在其它线程看来可以是乱序的(即感知顺序与程序顺序不同)?
      2. JMM规定:
        1. 对于long/double型以外的数据类型以及引用类型的共享变量进行读、写操作都具有原子性
        2. 对于volatile修饰的long/double型变量读、写操作也具有原子性
      3. 可见性和有序性问题:
        1. happen(s)-before关系
        2. JMM定义了一些happens-before关系的规则:
          1. 程序顺序规则
            1. 一个线程中按照动作的先后顺序
            2. happens-before关系与时间上的先后关系并无必然的联系
          2. 内部锁规则
          3. volatile变量规则
            1. 对一个volatile变量的写操作happens-before后续每一个针对该变量的读、写操作
          4. 线程启动规则
            1. 调用该线程的start方法happens-before该线程中的任何一个动作
          5. 线程结束规则
            1. 一个线程中的任何一个动作都happens-before该线程的join方法的执行线程在join方法返回之后所执行的任意一个动作
  3. Java多线程程序的性能调优
    1. Java虚拟机对内部锁的优化
      1. 优化主要包括:锁消除、锁粗化、偏向锁以及适应性锁
      2. 锁消除
        1. JIT编译器借助一种逃逸分析的技术来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程
        2. 即,如果可以,则不使用锁
      3. 锁粗化
        1. 合并为一个大的同步块,避免了一个线程的反复申请、释放同一个锁导致的开销;
        2. 可能会导致一个线程持有锁的时间变长
      4. 偏向锁
        1. 大多数锁并没有被争用,并且这些锁在其整个生命周期内至多只会被一个线程持有
      5. 适应性锁
        1. 即自旋锁,不暂停线程,而是执行一段无意义的代码;适合大多数线程对该锁的持有时间比较短的场景
    2. 锁(内部锁和显式锁)的开销:
      1. 上下文切换与线程调度开销
      2. 内存同步、编译器优化受限的开销限制可伸缩性
    3. 优化锁的思路:
      1. 不使用锁,或者降低锁的争用程度
      2. 减少锁被持有的时间
        1. 减小临界区的长度
      3. 降低锁的申请频率
        1. 降低锁的粒度
          1. 锁拆分技术
          2. 锁分段技术
            1. ConcurrentHashMap就是采用分段锁搞定的,


如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,我会持续更新后续章节学习笔记,可以进群366533258一起交流学习哦~

本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。