为什么要讲这个
缘起都来自于并发,粒度再变,解决的核心却很雷同.
两个主要关键点
- 资源共享
- 执行调度
多线程与JMM开始说起
多线程是如何操作共享内存的
读:线程得到CPU调度,线程从共享Heap区的实例变量读入线程独有的工作内存
改:线程处理工作内存中的值
写:线程将工作内存的值写回共享Heap区的实例变量中
存在什么样的问题呢?
首先:我们明白从执行调度的角度来说,线程何时被调度是无法控制的,由CPU宠幸决定
其次:这块共享区域,只要线程愿意,实际都是可见的
但是:可见是可见了,见到的东西是正确的东西么
所以:资源的共享 加上 执行调度的无序 导致了 可见性的正确问题.
该如何解决呢
我们请个交警了管理交通秩序吧.
既然无序导致了可见行的正确的问题,那么我们将其变得有序就行了.
这也就是我们常用的方法了,通过用关键字synchronized将代码锁起来
虽然线程调度无序,但是我们可以通过这个特殊关键字来保证抢到锁的人才能执行程序处理.
但synchronized是锁的意思么?其实不是,他是同步的意思.
所以加锁是方法,同步才是本质.也就是通过加锁的方式,线程1进入处理后,
通过读–>改–>写然后释放锁,线程2抢占到锁,线程2这个时候看到就是正确的共享变量的状态.
请个交警是政府行为,即OS底层操作的,资源浪费有点多,效率也有点低
那,不加锁了,我们把工作内存去掉吧.
仔细看来,JMM这种处理模型,有一个“缓存”作用的工作内存的存在,
且读改写是三个操作,并非原子操作,真因为这是可拆分的操作单元,
线程1在读或者改的时候,线程2也进入了读,那么,线程2读到东西就不是线程1操作后的结果,这个值是不正确的.那既然是那个操作会有问题,那么我们让缓存失效如何
volatile这个关键字就可以实现这个事情,它让缓存失效了,看上去,线程们都在直接操作共享区的实例变量了,解决了正确的可见性问题
但它只能操作与单个变量,使得这个变量瞬间的正确可见,但要是对于这个变量参与了一些非原子操作,那么共享的实例变量也是存在问题的.
比如i++,虽然没了工作内存和共享内存直接的读改写问题,
但是i++,等同与tmp=i,tmp=tmp+1,i=tmp这样三个操作,其实还是读–>改–>写 这三个操作,所以非原子操作仍将存在问题
CAS了解下
CAS是什么:Compare And Swap,
Compare的意思是内存实际的offset值与线程1进入处理后读到的值(称之为预估值)进行比较,如果相等,那么把新值Swap到内存中去,如果不想等则放弃修改
这个是java中的java.util.concurrent.atomic里边的使用到的核心算法.
另外一个重要的点就是这个包中的类型是使用volatile修饰的.
也就是volatile+CAS创建了atomic这个原子包.
其实这个volatile+CAS也是OS底层操作的,只不过比哪个正式的交警高效一点,资源浪费也少很多,估计是个合同工(–>其中有一个原因是省去了线程上下文切换消耗)
然:sync效率不高,atomic操作范围不大
那么我们来了解下同步锁吧.
Lock
优秀的线程安全容器
ConcurrentHashMap:底层CAS的分段锁的HashMap
CopyOnWriteArrayList:写时复制的ArrayList
优秀的锁能力
ReentrantReadWriteLock:可重入的读写分离锁
CountDownLatch:闭锁–>实施分而治之的好帮手