前面我们学习过线程不安全问题,我们通过给代码加锁来解决线程不安全问题,在生活中我们也知道有很多种类型的锁,同时在代码的世界当中,也对应着很多类型的锁,今天我们对锁一探究竟!
注意: 接下来介绍的锁策略不仅仅是局限于Java.任何和"锁"相关的话题,都可能会涉及到以下内容.这些特性主要是给锁的实现者来参考的.
我们普通的程序猿也需要了解⼀些,对于合理的使用锁也是有很大帮助的.
乐观锁: 在执行任务之前预期竞争不激烈,那就可以先不加锁,等后面如果真实发生了锁竞争再加锁
举个例子: 我很喜欢我的女友,在学校时,我们天天在一起,我不担心有冲突别人会占用她(并没有对她上锁),但是偶尔她会和她的好朋友出去,此时,我感觉到有人在和我竞争她,我就加锁,不让她离开我
悲观锁: 在执行任务之前预期竞争非常激烈(表示在执行任务的时候会发生锁冲突),必须先加锁再执行任务
举个例子: 我很喜欢我的女友,为了和她天天在一起,我对她上锁,这样当别人想要和她玩的时候,就会和我竞争,但是我持有锁,别人就会得不到她,显示她一直被我持有,哈哈哈
锁的核心特性"原子性",这样的机制追根溯源是CPU这样的硬件设备提供的.
轻量级锁: 加锁机制尽可能不使用mutex,而是尽量在用户态代码完成.实在搞不定了,再使用mutex.加锁的过程比较简单,用到的资源比较少,典型就是用户态
的一些操作(JAVA 层面就可以完成加锁)
举个例子: 有对象的男同胞,都会遇到的问题,就是和女友出去玩的时候,会等待女友的精心打扮(化妆),轻量级锁就是不停的自旋,一会问一下“宝宝,化好妆了吗”(只是一昧的催促女友,不干别的事情,消耗资源较少),可以第一时间知道女友啥时候化好妆可以出发
重量级锁: 加锁机制重度依赖了OS提供了mutex,加锁的过程比较复杂,用到的资源比较多,典型的就是内核态
的一些操作
举个例子: 当女友在化妆的时候,我们不去过问,而是做好自己的事情,准备好出去玩的所有东西(一直在帮忙做事,消耗了很多资源),等待女友的召唤(唤醒),不能第一时间知道女友啥时候化好妆
理解用户态和内核态:
想象去银行办业务.
在窗口外,自己做,这是用户态.用户态的时间成本是比较可控的.
在窗口内,工作人员做,这是内核态.内核态的时间成本是不太可控的. 如果办业务的时候反复和工作人员沟通,还需要重新排队,这时效率是很低的.
乐观锁是能不加锁就不加锁,从而导致他干活少,消耗资源也少,所以可以说乐观锁就是一种轻量级锁
悲观锁是任何时候都加锁,从而导致他干活多,消耗资源也多,所以可以说悲观锁就是一种重量级锁
自旋锁: 不停的检查锁是否被释放,如果一旦锁被释放就可以直接获取锁资源
挂起等待锁: 阻塞等待,等待到被唤醒
举个例子: 每周末,我要和我对象一起吃饭,我到了她宿舍楼下打电话问她啥时候下来,她说等一会,我就不停的打电话(一直在自旋,不停的检查锁的状态,她下楼了,我会第一时间发现)——>
自旋锁
,我不打电话了,然后去小亭子坐下来,等待(我就不能第一时间发现她下楼如果她下楼之后就需要喊我一声相当于通知我(唤醒),获取锁资源)——>阻塞:挂起等待锁
优缺点:
纯用户态的操作
,可以第一时间获取到锁,内核态的操作
,会生成对应的加锁指令,要等待唤醒,在等待的过程中会释放CPU资源自旋锁详情请看后续CAS详细介绍
公平锁: 先来后到,先排队的线程先拿到锁,后排队的线程后拿到锁,JAVA的JUC中有一个类专门实现了公平锁
非公平锁: 大家去抢,谁先抢到是谁的,synchronized是一个非公平锁
注意:
举个例子: 现实生活中如果要真正的公平:立法、执法、教育、环境都要发挥作用消耗更大的资源,实现公平锁的过程也是一样,需要用额外的逻辑去管理线程,做到先来后到
多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同⼀个锁,就会产生极大的性能损耗。所以读写锁因此而产生。
⼀个线程对于数据的访问,主要存在两种操作:读数据和写数据.
读写锁就是把读操作和写操作区分对待.Java标准库提供了ReentrantReadWriteLock 类
,实现了读写锁.
ReentrantReadWriteLock.ReadLock 类
表示⼀个读锁.这个对象提供了lock/unlock方法
进行加锁解锁.ReentrantReadWriteLock.WriteLock 类
表示⼀个写锁.这个对象也提供了lock/unlock方法
进行加锁解锁.其中,
注意,只要是涉及到"互斥",就会产⽣线程的挂起等待.⼀旦线程挂起,再次被唤醒就不知道隔了多久了.因此尽可能减少"互斥"的机会,就是提高效率的重要途径.
适用场景:
读写锁特别适合于"频繁读,不频繁写"的场景中.(这样的场景其实也是非常广泛存在的).
举个例子: 大学生必备学习通,老师会经常上课点名(读操作),发布学习资料(写操作),同学们看详细资料(读操作),读操作会涉及很多次,但是写操作偶尔几周一次,所以很适合读写锁
可重入锁: 对一把锁可以连续加多次,多次加锁也要多次解锁,不造成死锁
不可重入锁: 对一把锁可以连续加多次,造成死锁
相比于挂起等待锁,
是可重入锁.
可重入锁指的就是连续两次加锁不会导致死锁.
实现的方式是在锁中记录该锁持有的线程身份,以及⼀个计数器(记录加锁次数).如果发现当前加锁的线程就是持有锁的线程,则直接计数自增.