接下来对锁的概念再次进行深刻的介绍
以前反复的提到锁,一般的理解就是,锁---互斥---同步---阻塞
其实这是经常使用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案
抗战电影中,常常出现为了阻止日本人炸桥?炸路?的场景,这只是阻止日本人的一种手段,若是大喊一声TMD滚蛋,日本人就走了,还炸桥干吗?
用锁是为了线程安全,而不是为了上锁,上锁是一种途径,独占锁则是“上锁”的其中一种形式
若是有更优雅的上锁方式,天然没必要要每次都简单粗暴的使用独占锁,不是嘛
从几个维度能够大体分为下面几种
分类是以锁为核心,而延展出来的优雅的使用方式
乐观锁与悲观锁
- 乐观就像小米的宣传语-永远相信,美好的事情即将发生;
- 而悲观就像透过有色眼睛看世界,永远都带有颜色;
对应到程序中
使用锁是为了什么?由于线程之间会共享数据,共享数据就必定会出现问题吗?固然是几率的
- 两我的去同一个水井打水,若是每次都错开,那么美好一直存在
- 若是每天赶到同一个时间点,我相信迟早必有一战.....
因此如何看待在多线程共享数据中,出现的几率性竞争问题?
- 乐观的眼光就是坚信绝大多数时候是没问题的,只须要最后发生修改或者操做时进行校验,好比校验是否被修改过,而后再去进一步处理
- 悲观的眼光就是坚信确定会有问题,因此我就一直加锁,只要一直锁住反正确定不会出现问题
因此你看,乐观锁其实能够并无锁,是在逻辑上实现了锁的业务
假设这样一种比较极端的场景
A,B两个线程,共享数据c
A线程每分钟都会对c进行操做(一天24*60次),B天天都会对c进行一次操做
试想一下乐观锁和悲观锁之间的性能差别?
在这个场景中能够认为即便没有任何防范措施,天天正确的几率也会大于99.9%(不考虑一次错误后致使的后续连锁错误),若是选用悲观锁,将会有多少无谓的损耗?
synchronized就是悲观锁,他会为你保障百分百的加锁与同步
公平锁与非公平锁
公平与非公平的字面意思你们都很清晰,没有人理解起来有难度
好比语文老师对你和同桌的态度一视同仁、好比领导对你和同事的年终奖你以为不公平....
可是,对于锁来讲,这个公平与不公平针对的是什么?
以前在监视器概念中提到的,线程若是在某个监视器的等待集合中,那么若是当前线程执行结束后,谁应该被选做下一个进入监视器的线程呢?
这就是锁的公平性针对的点
- 对于锁的请求,每一个线程老是有一个先来后到的前后顺序,若是按照前后顺序,那么就是公平锁
- 若是不按照前后顺序,随机的或者按照什么算法优先级等选择,那么就是非公平锁
除非有额外的公平性要求,不然不该该使用公平锁,由于对性能是有损耗的
独占锁和共享锁
- 若是一个锁仅仅只能被一个进程拥有,那么他就是独占的;
- 若是一个锁能够同时被多个线程拥有,那么他就是共享的;
独占锁会保障任什么时候候都只是有一个线程进行访问,ReentrantLock就是独占锁,synchronized的原理也是独占锁
而读写锁ReadWriteLock就是共享锁,能够同时容许多个读线程进行操做
可重入锁
重入,就好像是你结帐后餐馆老板对你说的下次再来!
当一个线程想要获取一个被其余线程独占的锁时,你须要等待,可是若是是本身已经得到的锁呢?
答案是你能够屡次获取,这就是可重入
好比一个类中有两个同步的实例方法,而锁对象都是当前对象this
若是不可重入会发生什么?
调用了A方法以后,想要调用方法B可是锁却被本身占有了,若是不可重入,就成了本身等本身,岂不是傻子?
可重入锁在内部维护了一个计数器,用于记录重入次数
本身得到一次,那么计数器+1,释放一次计数器-1,若是计数器为0,说明该线程释放了该锁,不然,锁仍旧被该线程持有
自旋锁
在以前线程简介中有提到,Java线程是内核级映射的线程(1.2吧?以后)
若是一个线程请求获取一个锁时,并不能获取到,那么将会进入阻塞状态,也就是会被切换到内核态而后挂起
当该线程获取到锁时,又须要切换到内核状态进行唤醒,说白了须要用户状态与内核状态的切换
并且,这个状态的切换,仍是比较消耗性能的
怎么办?
有一种解决办法就是线程继续运行,过一下子再次尝试锁的获取
怎么作到的?
其实就是至关于CPU空跑,而不是直接将线程进行挂起
因此说至关于牺牲了CPU的时间片,换取内核状态的开销,这就涉及到一个平衡点的问题
若是线程之间竞争不激烈,可能下次的尝试获取就成功了
可是若是线程之间竞争很是激烈,下次仍是抢不到,下下次仍是抢不到......会发生什么状况?
那就是浪费了太多的CPU时间片,并且,你也不能永远的像“死循环”同样尝试呀,因此通常会有一个时长或者次数,总之有个上限
如今对自旋锁应该有了必定的了解了,自旋就是本身在那边不停地打转,不给糖吃就打滚的哭
书面点的说法:
当前线程获取锁时,若是发现锁已经被其余线程占有,并不会立刻阻塞本身,在不放弃CPU的状况下,屡次尝试
刚才也说明了,对于线程是否竞争激烈,自旋锁有着不一样的反应,也说不定会致使CPU白白浪费了时间片,因此要根据业务来
适应性自旋
刚才说到,对于自旋不能无止境的,那就是相似“死循环”,因此都有限制,一般用次数,好比规定次数为5
可是实际状况中,可能有时常常1次就可以成功,也可能常常5次了尚未成功,若是1次就成功的还好,若是说限制为5次,每次都5次后仍是失败,这就是纯粹的白忙活
因此后来出现了自适应的自旋锁,自旋的次数(限制)再也不是固定的了
- 若是对于某个锁,自旋不多成功得到过,那在之后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源
- 若是对于某个锁,自旋等待刚刚成功得到过锁,那么将会认为此次也极可能是成功的,因此将会容许自旋,甚至容许更屡次数(时间)的自旋等待
对于适应性自旋,大体逻辑是这样,具体细节由具体算法决定
再次强调
对于自旋,表面上看是减小了阻塞的发生,进而能够减小状态切换的性能损耗,可是这是以浪费CPU为代价的
因此并不能认为自旋锁是治疗百病的良药
实际使用中,不能只看到带来的好处,也须要关注付出的代价,而对于适应性自旋,只是对于自旋的“死板”的一种调优而已
小结
以上分类只是就某一个维度对锁的概念以及应用的分析
他们概念上不是彻底隔离的,不是说就存在那么几种锁,A,B,C....A就是A,B就是B
好比人类都是人,可是分为男人、女人,还能够分为好人、坏人等,好人有男女,坏人也有男女
好比独占锁属于悲观锁,独占就是要保障同一时间只有一个线程操做,其余线程必须等待
共享锁就属于乐观锁,由于他放宽了加锁的条件
理解锁的分类有助于后续关于其余高级工具、类的理解与学习