学习多并发第一课--多并发基础
学习多并发第一课--多并发基础
目录
1.为什么需要并发编程
为了提高运行效率,程序运行会采用多线程运行,当多个线程操作同一个资源时,会涉及到一系列的问题。
2.并发编程需要解决的问题
-
原子性问题
操作系统切换任务的时候,可以发生在每一次CPU指令结束 操作系统在运行时,保证的原子性是指令级别的,而不是高级语言JAVA的运算符 例如 i++,是三个指令
-
可见性问题
可见性是有CPU缓存导致的,多核CPU运行时,每核CPU都有自己的缓存,在缓存没和内存进行同步时,一个线程对共享资源的改变,是其他线程看不到的.
-
有序性问题
在执行程序的时候,CPU会对指令重新排序。重新排序的结果不会影响单线程的运行结果,但是当多线程运行的时候,会产生诡异的BUG
3.怎么解决问题(JMM模型如何解决可见性,有序性问题)
3.1并发编程的关键目标(通信和同步)
- 通信:是指不同线程之间以何种方式交换信息**(可见性问题)**
- 同步:是指如何控制不同线程之间的操作发生的相对顺序**(有序性问题)**
3.2 并发编程的内存模型
共有两种内存模型,共享内存模型和消息传递模型,Java才用的是共享内存模型
- 共享内存模型下,线程之间共享程序的公众状态,采用写-读内存的方式进行隐式通信
- 共享内存模型下,同步是显示进行的,程序员必须用显示指定代码需要在线程之间互斥执行
3.3 JMM模型
JMM如何解决可见性问题
Java的内存共享模型叫JMM模型, Java线程之间的通信由JMM控制,即JMM决定了一条线程对共享资源的操作何时对另一个线程可见 JMM定义了线程和主内存之间的抽象关系,通过控制主内存与线程的"本地内存"(抽象概念)来保证内存的可见性
JMM如何解决有序性
为了提高性能,编译器和处理器在不影响语义的情况下会对指令重排序,重排序的类型有三种, **1.编译器优化重排序:编译器在不改变单线程程序语义的情况下,会对指令重排序
2.指令级并行重排序:现代处理器采用指令并行技术将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
3.系统内存重排序:**由于处理器使用缓存和读/写缓存区,这使得加载和存储操作看上去可能是在乱序执行
第一种是编译器重排序,后两种是处理器重排序
针对编译器重排序
JMM的编译器重排序规则会禁止特定类型的指令重排序
针对处理器重排序
JMM会在不同处理器插入不同种类和数量的内存屏障
3.4 内存屏障类型(重排序问题)
CPU内存屏障
- LoadLoad :禁止读和读的重排序
- StoreStore:禁止写和写的重排序
- LoadStore:禁止读和写的重排序
- StoreLoad:禁止写和读的重排序
JMM内存屏障
有一个Unsafe类下有三个方法,JMM用于插入内存屏障
- public native void loadFence(); LoadLoad +LoadStore
- public native void storeFence() StoreStore+LoadStroe
- public native void fullFence();; loadFence()+StoreFence()+StoreLoad
3.5 happens-before规则(可见性问题)
JMM使用happens-before规则来阐述操作之间的可见性问题,以及什么时候不能重排序 在JMM中,如果一个操作需要对另一个操作可见,两者之间就要有happens-before规则
- 程序顺序规则:
- synchronized规则:
- volatile规则:
- 传递性:
- statr()规则:
- join()规则:
- 关键字Volatile
基本特性:
- 保证内存的可见性
- 禁止指令重排序
内存语义:
-写内存语义:当写一个volatile变量后,JMM会把该变量刷新到主内存中
-读内存语义:当读一个volatile变量时,会把线程内的本地内存置为无效,从主内存中读取
实现机制:
分别在读写操作的前后插入内存屏障
- 在volatile读操作之前加入 LoadLoad
- 在volatile读操作之后计入LoadStore
- 在volatile写操作之前加入StoreStore
- 在volatile写操作之后加入StoreLoad
5.锁
内存语义
-在线程拿到锁时 JMM会把本线程的缓存置为无效,去主内存中读取
-在线程释放锁时,JMM会把该线程缓存中的共享变量刷新到主内存中
实现机制:
-synchronized: 采用Mark Work+CAS实现,存在锁升级的情况
-Lock; 采用volatile+CAS实现的,存在锁降级的情况 核心是AQS
synchronized和lock的区别
- synchronized不需要手动释放锁,lock需要手动释放锁
- synchronized无法获取锁的状态,而lock可以
- synchronized 是java内置关键字,而lock是一个类
- synchronized无法中断,为非公平锁而lock可以中断,可以设置公平和非公平
- synchronized 不同线程只有一个线程可以获取锁,其他线程会陷入阻塞直到获取锁,而lock有阻塞锁也有非阻塞做,阻塞锁还有尝试设置,
- synchronized适合锁少量代码块,lock适合锁大量代码块