并发编程
并发:在一段时间内,多个事情交替执行
并行:多件事情在同一时刻同时发生
在例如买票,抢购等场景下,有大量的请求访问同一个资源.会出现线程安全的问题,需要通过编程来控制解决多个线程依次访问资源,称为并发编程.
并发编程的根本原因
Java 内存模型(Java Memory Model,JMM)
是Java虚拟机的一种工作模式.将内存分为主内存和工作内存.变量数据存储在主内存中,线程操作变量时,会讲述内存中的数据复制一份到工作内存中.在工作内存中完成操作后了,再写回到主内存中.
多线程核心根本问题
不可见性
多个线程分别同时对共享数据进行操作,彼此之间不可见,操作完写回主内存时可能出现问题
无序性
为了优化性能对一些代码指令的执行重新排序
非原子性
原子性是一个或多个操作在CPU执行的过程中不被中断的特性.线程切换导致原子性出现问题.
例如: i++ 至少要三条CPU指令
1.先从主内存加载到工作内存
2.操作数据
3.再从工作内存写回到主内存
解决多线程问题办法
让不可见变为可见
让无序变为有序
非原子执行变为原子
volatile关键字
volatile修饰的变量被一个线程修改后,能在其他线程中立即可见.
volatile修饰的变量,在执行时不会被重新排序
volatile不能解决原子性问题
底层实现原理
在底层使用指令级别进行控制.有序性实现:volatile修饰的变量在操作前,添加内部屏障,不让指令干扰.可见性实现:通过Lock前缀指令+MESI缓存一致性协议来实现.发送一个Lock给CPU,CPU在执行完操作后会立即将值刷新到内存,MESI将数据写回到主内存,其他工作内存嗅探后,将自己工作内存数据过期,重新从主内存中读取最新的数据.
加锁实现原子性
通过加锁的方式,让线程互斥来保证一次只有一个线程对共享资源访问.
synchronized : 关键字,修饰代码块,方法.自动获取锁,自动释放锁
ReentrantLock : 类,只能对某段代码修饰.需要手动加锁,手动释放锁.
原子类实现
在java中还提供一些原子类,在低并发情况下使用,是一种无锁实现原子性.采用CAS机制(Compare-And-Swap 比较并变换)
原理
采用自旋思想.第一次获取内存值到工作内存中, 存储起来作为预期值, 然后对数据进行修改,再将工作内存中值写入到主内存.在写入之前要进行判断,用预期值与主内存中的值进行比较, 如果预期值与主内存中值一致, 说明没有其他线程修改, 将更新数的值,写入到主内存. 如果预期值与主内存中值不一致, 说明其他线程修改了主内存的值, 这时就需要重复操作整个过程.
优点: 不加锁,所有的线程都可以对共享数据操作;适合低并发时使用;由于不加锁,其他线程不需要阻塞,效率高.
缺点:大并发时,不停自旋判断,导致cpu占用率高;会出现ABA问题(通过设置版本号,每次操作改变版本号).