并发编程

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

你可能感兴趣的:(并发编程)