美文网首页
并发编程

并发编程

作者: android小菜鸡一枚 | 来源:发表于2020-08-05 00:45 被阅读0次

    01 进程和线程

    image.png
    image.png

    02 并行和并发

    image.png
    image.png
    image.png
    image.png

    03 Java线程

    image.png
    image.png
    image.png
    image.png
    image.png

    04 栈与栈帧

    image.png
    image.png

    05 线程上下文切换

    image.png

    06 线程常见方法

    image.png
    image.png
    06-1 sleep与yield
    image.png
    06-2 线程优先级
    image.png
    06-3 join
    打印0
    image.png
    image.png
    r1:10 r2:20 cost:2000
    06-4 interrupt方法

    打断sleep线程,清空打断标记


    打断标记:false

    打断正常运行的线程,不会清空打断状态


    image.png
    06-5 两阶段终止模式
    image.png
    image.png
    image.png

    打断park线程,不会清空打断状态


    image.png
    06-6 不推荐使用的方法
    image.png

    07 主线程和守护线程

    有一种特殊的线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束;
    垃圾回收器线程就是一种守护线程


    image.png

    08 线程状态

    image.png

    从Java API层面描述,分六种状态


    image.png

    共享模型之管程

    09 临界区Critical Section

    image.png
    image.png

    竞态条件Race Condition
    多个线程在临界区内执行,由于代码的执行不同导致结果无法预测,称之为发生了竞态条件;

    10 synchronized

    image.png
    image.png
    image.png

    11 方法上的synchronized

    image.png

    12 “线程八锁”

    • image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • image.png

    13 变量的线程安全分析

    image.png

    14 常见的线程安全类

    image.png

    15 不可变类线程安全性

    image.png

    16 买票练习

    image.png
    image.png
    image.png

    17 转账练习

    image.png
    image.png

    18 Monitor概念

    一个java对象在内存中由两部分组成,java对象头和对象中的成员变量;
    普通对象Object Header有Mark WordKlass Word组成

    image.png
    Mark Word的结构:
    image.png

    Monitor被翻译成监视器或管程
    每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象obj上锁(重量级)之后,obj对象和操作系统提供的Monitor对象相关联,该对象头的Mark Word中就被设置指向Monitor对象的指针;

    image.png

    注:synchronized必须是进入同一个对象的monitor才有上述的效果;不加synchronized的对象不会关联监视器,不遵从以上规则;

    19 轻量级锁

    • image.png

      如果有竞争,轻量级锁会升级为重量级锁
      对上述代码的分析:

    • image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • image.png

    20 锁膨胀

    • image.png
    • image.png
    • image.png

    21 自旋优化

    重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步快,释放了锁),这时当前线程就可以避免阻塞。

    • 自旋成功的情况:


      image.png
    • 自旋失败的情况:


      image.png
    image.png

    22 偏向锁

    • image.png
    • image.png
    • image.png
    22-1 偏向状态
    • image.png
    • image.png

      调用对象的hashcode,会禁用这个对象的偏向锁;

    22-2 撤销-调用对象hashCode
    image.png
    22-3 撤销-其他线程使用对象

    当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

    22-3 批量重偏向
    image.png
    22-4 批量撤销
    image.png
    22-5 锁消除

    加锁是有一定的性能损耗的,a和b方法执行的时间相近,java运行时有一个JIT,即时编译器,它会对java字节码做进一步优化,其中一个优化就是分析局部变量是否能优化,方法b中的对象o,根本不会逃离方法的作用范围,这种情况意味着不可能被共享,那么加synchronized加锁没有任何意义,JIT会将synchronized代码优化掉,有个-XX:EliminateLocks开关控制


    image.png

    23 wait/notify原理

    image.png
    23-1 API介绍
    image.png

    不能直接调用对象obj的wait方法,会报illegalMonitorStateException异常,必须先获取到对象锁


    image.png

    24 wait/notify的正确使用姿势

    24-1 sleep(long n)和wait(long n)的区别
    image.png

    它们的共同点时都会进入TIMED_WAITING状态

    • sleep不会释放锁,wait会释放锁


      image.png
    • wait/notify使用模板


      image.png
    24-2 设计模式-保护性暂停
    image.png
    • 代码示例


      image.png
      image.png
      image.png
    24-3 保护性暂停-扩展-增加超时
    • image.png
    24-4 join原理

    一个线程等待另一个线程的结束

    24-5 保护性暂停-扩展-解耦等待和扩展
    image.png
    • 代码分析


      image.png
      image.png
      image.png

    25 异步模式之生产者/消费者

    image.png
    代码设计
    • Message


      image.png
    • MessageQueue


      image.png
      image.png
      image.png
    • 测试


      image.png

    26 Park&Unpark

    image.png
    代码分析
    • image.png
    • 测试结果


      image.png
    • unpark即可以在park之前调用,也可以在park之后调用,都是恢复某个线程的运行


      image.png
    特点
    image.png
    原理之park&unpark
    • image.png
    • park


      image.png
    • unpark


      image.png
    • 先unpark后park情况


      image.png

    27 线程状态切换

    image.png
    • image.png
      image.png
      image.png
      image.png
      image.png
      image.png

    28 多把锁

    多把不相干的锁
    代码分析

    • image.png
      image.png

      问题:串行,并发度比较低,sleep和study是不相干的,改进后使用两个房间加锁,增强程序的并发性


      image.png
      image.png

    29 活跃性

    29-1 死锁
    • image.png
      image.png
    • 运行结果
    29-2 定位死锁
    • image.png
    • image.png
    29-3 哲学家就餐问题
    • image.png
    • ChopStick


      image.png
    • Philosopher


      image.png
    • test


      image.png
    29-4 活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束

    • 两个线程都无法停止,这种现象称之为活锁


      image.png
    29-5 饥饿
    • image.png
      image.png

      就是有的线程得不到执行的机会,如何解决线程饥饿的现象,后面使用ReentrantLock解决

    30 ReentrantLock

    • image.png
    30-1 可重入
    • image.png
    • 代码分析


      image.png
      image.png
      image.png
    30-2 可打断

    ReentrantLock支持可打断,避免死等,减少死锁的发生

    • image.png
      运行结果
    30-3 锁超时
    • 情况1


      image.png
      运行结果
    • 情况2


      image.png
      image.png
      运行结果
    30-4 解决哲学家就餐问题
    • 让Chopstick筷子对象继承ReentrantLock


      image.png
    30-5 公平锁

    ReentrantLock默认是不公平的,可以通过构造方法设置是否是公平锁;
    公平锁一般没有必要,会降低并发度;
    sychronized的monitor锁也是不公平锁,当一个线程持有锁,其他的线程会进入阻塞队列去等待,当锁的持有者释放锁的时候,这些线程会一拥而上, 谁先抢到了就会成为锁的主人,而不会按进入阻塞队列的顺序先来先得。

    30-6 条件变量
    • image.png
      image.png
    • 代码示例


      image.png
      image.png
      image.png
      image.png
      运行结果
    30-7 同步模式之顺序控制

    固定运行顺序

    • wait/notify方式


      image.png
      image.png
    • park/unpark方式


      image.png
    • 结果是先打印2,后打印1


      运行结果
    31 交替输出
    • image.png
    31-1 wait/notify方式
    image.png
    image.png
    • 第一个线程打印a,第二个线程打印b, 第三个线程打印c,运行结果就是abcabcabcabcabc


      image.png
      image.png
    31-2 await/signal方式
    • image.png
    • 1s后主线程唤醒a休息室中的线程


      image.png
    31-3 park/unpark方式
    • image.png
    • 主线程先唤醒t1线程


      image.png

    32 小结

    image.png

    33 java内存模型

    image.png

    34 可见性

    • 分析个现象,线程并不会像预想的那样停下来


      image.png
    • 从内存的角度分析上面发生的问题


      image.png
      image.png
      image.png
    • 解决方法
      使用volatile,线程就不能从缓存中读取了,每次必须到主内存中读取变量的最新值,保证了共享变量在多个线程之间的可见性
      它可以修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主内存中获取它的值,线程操作volatile变量都是直接操作内存。
    • 使用synchronized也可以解决可见性问题,区别就是synchronized需要创建monitor锁,是重量级锁,volatile是轻量级操作


      image.png

    35 可见性vs原子性

    image.png
    image.png
    synchronized语句块即可以保证代码块的原子性,也同时保证代码块内变量的可见性,但缺点是synchronized是属于重量级操作,性能相对较低。

    36 两阶段终止模式-volatile

    在一个线程T1中如何优雅的终止线程T2,这里的优雅指的是给T2一个料理后事的机会

    • image.png
    • 测试代码和运行结果


      image.png
      image.png

    37 同步模式之Balking

    image.png
    • 保证监控线程只启动一次


      image.png
      image.png

    38 有序性

    38-1 指令重排
    image.png
    image.png
    image.png image.png
    38-2 指令重排现象
    • r.r1值可能为1,4或0


      image.png
    • 压力测试结果,有可能因为指令重排序导致结果为0


      image.png
    38-3 禁止指令重排序
    • 加上volatile


      image.png

    39 volatile原理

    volatile的底层原理是内存屏障,Memory Barrier
    对volatile变量的写指令后会加入写屏障
    对volatile变量的读指令前会加入读屏障

    39-1 如何保证可见性
    image.png
    39-2 如何保证有序性
    image.png
    39-3 double-checked locking问题
    • synchronized加的范围太大会对性能有影响,只要调用一次getInstance方法,就会进入一次同步代码块,实际上这个单例创建出来后只有第一次需要线程安全保护,单例对象已经创建好了之后就不需要线程安全保护了


      image.png
      image.png
    • synchronized虽然可以保证同步代码块内的原子性,可见性和有序性,但是Singleton()构造方法执行指令和赋值的指令会产生指令重排序,synchronized代码块中的代码仍然可以重排序的,volatile可以防止重排序;如果这个共享变量INSTANCE完全被synchronized保护的话,就不会有原子性,可见性和有序性的问题;


      image.png

    40 happens-before规则

    image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • image.png
    • volatile防止指令重排


      image.png

    41 习题

    • volatile仅仅保证共享变量的可见性,init方法即有读取也有写入共享变量initialized的代码,不能保证多行代码执行的原子性,doInit方法会被调用多次,解决方法是使用synchronized


      image.png
    • 线程安全单例问题


      image.png
      image.png
      image.png
      image.png
    • 问题1,synchronized代码块里指令还是会重排序的,构造方法指令和前面的赋值指令有可能被重排序,那么第二个线程进入synchronized代码块之前,获取这个对象引用,可能还没有调用构造方法,所以要保证指令不要发生重排序


      image.png
    • 问题一是懒汉式,类加载本身就是懒惰的 ,类只有在第一次用到才会触发类加载操作;问题二对静态变量的赋值操作是由JVM保护线程安全性


      image.png

    42 保护共享资源-无锁实现

    • 使用AtomicInteger


      image.png

    43 CAS与volatile

    image.png
    image.png
    • CAS需要volatile的支持
      获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰;
      它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存,即一个线程对volatile变量的修改,对另一个线程可见
      volatile仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)

    • 为什么无锁效率高


      image.png
    • CAS特点


      image.png

    44 原子整数

    J.U.C并发包提供了:
    AtomicBoolean
    AtomicInteger
    AtomicLong

    • 原子加减法运算
      getAndIncrement是先获取,再自增;incrementAndGet是先自增再获取;


      image.png
    • 之前例子修改


      image.png
    • updateAndGet方法
      i * 10 ,结果为50


      image.png
    • updateAndGet实现原理


      image.png

    45 原子引用

    • AtomicReference示例


      image.png
      image.png
    • ABA问题
      主线程无法感知其他线程对该共享变量的修改


      image.png
      image.png
    • AtomicStampedReference


      image.png
      image.png
    • AtomicMarkableReference


      image.png
      image.png
      image.png

    46 原子数组

    • 多线程下普通数组是没有线程安全的


      image.png
      image.png
    • 使用原子数组


      image.png
      image.png

    47 字段更新器

    image.png
    image.png

    48 原子累加器

    • 使用AtomicLong累加实例


      image.png
      image.png
    • LongAdder示例


      image.png
      image.png
    • LongAdder性能提升的原因


      image.png
    源码之LongAdder
    image.png
    • cas锁的原理


      image.png
      image.png

    49 Unsafe

    Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获得。

    • Unsafe CAS操作


      image.png
      image.png
      image.png
    • 模拟实现原子整数类
      UnsafeAccessor:


      image.png

      MyAtomicInteger:


      image.png
      image.png

    50 不可变对象-使用

    SimpleDateFormat使用


    image.png

    不可变类DateTimeFormatter,保证了在多线程下的线程安全


    image.png

    51 不可变对象-设计

    image.png
    • final的使用
      发现该类,类中所有属性都是final的;
      属性用final修饰保证了该属性是只读的,不能修改;
      类用final修饰保证了该类中的方法不能被覆盖,防止子类无意破坏不可变性;
    • 保护性拷贝
      通过创建副本对象来避免共享的手段称之为保护性拷贝defensive copy.

    52 享元模式

    image.png
    image.png

    2.2 String字符串
    2.3 BigDecimal BigInteger

    53 自定义线程池

    image.png
    • 1.BlockingQueue


      image.png
      image.png
      image.png
      image.png
      image.png
    • 2.ThreadPool 线程池


      image.png
      image.png
      image.png

    54 线程池

    • 1.线程池状态


      image.png
      image.png
      1. 构造方法


        image.png
        image.png
        image.png
      1. 固定大小线程池


        image.png
        image.png

        线程工厂影响的是线程的名称

    • 4.带缓存线程池


      image.png
    • 5.单线程线程池


      image.png

      执行任务即使遇到了异常,后面的任务不受影响


      image.png
    • 6.提交任务


      image.png

    submit():


    image.png

    invokeAll():


    image.png
    image.png

    invokeAny():


    image.png
    • 7.关闭线程池
      shutdown并不会取消正在执行的任务,也不会影响在阻塞队列里的任务,并不会阻塞主线程其他代码的执行


      image.png
    • 任务调度线程池

    54 异步模式之工作线程

    • 1.定义


      image.png
      image.png
    • 2.饥饿问题
      代码示例饥饿问题,线程数不足导致的代码无法继续往下执行:


      image.png
      image.png

    饥饿问题解决:
    不同任务使用不同的线程池


    image.png
    image.png
    • 3.创建多少线程池合适


      image.png
      image.png

    相关文章

      网友评论

          本文标题:并发编程

          本文链接:https://www.haomeiwen.com/subject/fcakrktx.html