并发编程的起源
CPU 内存 IO设备速度严重差异
提高效率的方式:
(1)CPU增加了缓存
(2)操作系统增加了进程,线程以分时复用CPU 进而均衡CPU 与iO设备的速度差异
Java采用抢占式线程调度:如果一个线程申请IO 或者申请一个被其他线程占用的资源 就会进入阻塞状态 让出CPU 待准备完成OS会把这个休眠的线程唤醒,唤醒后就有机会重新获得CPU使用权
(3)编译程序优化指令顺序
这三种方式也带来了问题
可见性:工作区和共享区数据更新应该立刻能被看到
原子性:该操作要么已经执行完成要么尚未发生,其他线程不能得到操作的中间结果
有序性:重排序可能会导致多线程程序出现非预期操作
(重排序)是对内存访问有序操作的一种优化 :1 指令重排序 :主要是由JIT编译器,处理器引起的,指程序顺序与执行顺序不一致
2存储子系统重排序:
由高速缓存(是CPU中为了匹配与主内存处理速度不匹配而舍设计一个高速缓存),写缓冲器(用来提高高速缓存操作的效率)引起的,感知顺序与执行顺序不一致
重排序要保证单线程程序的正确性(貌似串行语义) 所以有数据依赖关系的指令不会重排
如果两个指令(操作)访问同一个变量,其中一个有写操作这两个指令就存在数据依赖关系
一些方法
volatile
volatile 修饰变量关键字可以保证可见性与有序性
(1)当对volatile变量执行写操作后JMM会把工作内存中的最新变量值强行刷新到主内存,写操作会导致其他线程里的缓存无效(CPU嗅探总线,主存中更改的数据地址与自己缓存对比,若一致则失效)
(2) 防止指令重排 在volatile前后加上内存屏障 (各种屏障都是保证同步,简单来说在屏障之后的写操作必须等待屏障之前的写操作完成才可以执行,读操作则不受影响)
缺点不具有原子性
volatile的实现是轻量级的 性能优于 synchronized
和CAS结合可以保证原子性
CAS
乐观锁思想 CAS是一条CPU并发原语,这个过程是原子性的,
例如AtomicInteger 的compareAndSet(“期望值”,“设置值”) 期望值为目标值一致时,修改目标变量为设置值 返回false和最新主存的变量值
底层原理: 调用Unsafe类中的CAS方法 JVM会帮我们实现出CAS汇编指令
这是一种完全依赖于硬件的功能,通过它实现原子操作
CAS三大问题
a)CAS长时间一直不成功,会给CPU带来很大的开销,在Java的实现中是一直通过while循环自旋CAS获取锁
b)ABA问题 多线程操作了 虽然是预期值但不代表过程是没有问题的
A在N次计算中改变value的值
A执行过程中可能会还原value最初的值
B计算后比较主存值与自身value值一致,进行修改 破坏原子性
用状态戳或者时间戳 AtomicStampedReference类
c)只能保证一个共享变量的原子操作
可以用锁 或者合并变量 JDK提供了AtomicReference类可以把多个变量放在一个对象里进行CAS操作
原子类思想都是volatile加保证读-修改-写操作的原子性 可以把原子变量类看做增强的volatile变量
synchronize
1 synchronized 隐性锁 依赖monitor
2 每个对象会与一个monitor相关联
(1)当监视器被占用时,就会处于锁定状态,监视器的获得过程是排他的。如果某线程已经占用了监视器,则其他线程会进入阻塞状态等待锁的释放
(2)执行完成退出监视器
修饰实例方法 修饰类方法 修饰代码块 (注意锁粒度)
优化:
同步器AQS
底层CAS(抽象队列同步器)定义了一套多线程访问共享资源的同步器框架
利用CLH队列锁实现 将获取不到线程的进程放入
JMM(Java内存模型)
JVM中的共享数据可能被分配到CPU中的寄存器中,主内存RAM中
若分配到寄存器中,每个CPU都有自己的 一个CPU不能读取其他CPU上的内容,如果两个线程分别运行在不同CPU上,无法看到数据的变化
CPU不直接从主存读取数据,先把RAM中数据读到Cache缓存中再把Cache的数据读到寄存器中,CPU中线程对数据更新,可能只是更新到写缓冲器,还没有到达Cache更不用说主存 分配到主存中 运行在另一个CPU中的线程无法看到共享数据的更新
CPU具有缓存同步 共享数据的更新必须被写入cache 这个过程就是冲刷处理缓存
JMM对这些进行规定 :每个线程之间的共享数据都存储在主内存中
每个线程都有一个私有的工作内存(是一个抽象的概念,他涵盖寄存器,写缓冲器,其他硬件的优化)
每个线程从主内存中把数据读取到本地工作内存中,在工作内存中保存共享数据的副本,工作内存仅对当前线程可见
UnSafe
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。 Java中的Unsafe类为我们提供了类似C++手动管理内存的能力,同时也有了指针的问题。
首先,Unsafe类是”final”的,不允许继承。且构造函数是private的:因此我们无法在外部对Unsafe进行实例化。通过反射获取Unsafe
有时间可以具体了解下Uusafe