1. 锁策略
锁策略是,在加锁的过程中,处理冲突的过程中,涉及到的一些不同的处理方式
此处的锁策略,并非是 java 独有的
这里的锁策略,一般是“设计锁”所需要的知识
2. 乐观锁 和 悲观锁
乐观锁:在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候就不会做太多的工作
加锁过程做的的事情比较少,加锁的速度可能就比较快,但是很容易引入一些其他的问题(但是可能会消耗更多的 cpu 资源)
悲观锁:在加锁之前,预估当前锁冲突出现的概率比较法,因此加锁的时候,就会做跟多的工作
做的事情更多,枷锁的速度可能更慢,但是整个过程中不容易出现其他问题
3. 轻量级锁 和 重量级锁
轻量级锁:加锁的开销小,加锁的速度更快
轻量级锁,一般就是乐观锁
重量级锁:加锁的开销更大,加锁速度更慢
重量级锁,一般就是悲观锁
轻量重量级锁,是加锁之后,对结果的评价
悲观乐观锁,是加锁之前,对未发生的事情进行的评估
整体来说,这两种角度,描述的是同一个事情
4. 自旋锁 和 挂起等待锁
自旋锁:是轻量级锁的一种典型实现
进行加锁的时候,搭配一个 while 循环,如果加锁成功,自然循环结束
如果加锁不成功,不是阻塞放弃 cpu,而是进行下一次循环,再次尝试获取到锁
这个反复快速执行的过程,就称为“自旋”
一旦其他线程释放了锁,就能第一时间拿到锁
同时,这样的自旋锁,也是乐观锁
使用自旋的前提,就是预期锁冲突黛绿不大,其他线程释放了锁,就能第一时间拿到
如果当前加锁的新城特别多,自旋意义就不打了,白白浪费 cpu
挂起等待锁:就是重量级锁的一种典型实现
同时也是一种悲观锁
挂起等待的时候,需要内核调度器介入,这个时候真正拿到锁需要花费的时间会很多
同时,这个锁也是一种悲观锁,这个锁可以适用于锁冲突激烈的情况
5. 普通互斥锁 和 读写锁
普通互斥锁:类似于 synchronized ,操作涉及到 加锁 和 解锁
读写锁:这里的读写锁,把加锁分为两种情况
- 加读锁
- 加写锁
读锁和读锁之间,不会出现锁冲突(不会堵塞)
写锁和写锁之间,会出现锁冲突(会堵塞)
读锁和写锁之间,会出现锁冲突(会堵塞)
也就是:读的时候,不能写,只能读;写的时候,不能读,也不能写
为什么要引入读写锁?
如果两个线程读,本身就属于线程安全,不需要互斥
如果使用 synchronized 这种凡是加锁,两个线程读,也会产生互斥,产生阻塞
如果完全给读操作不加锁,也不行,因为一个线程读一个线程写,很可能会读到写了一半的数据
这个时候引入读写锁,就可以解决上述问题
在实际开发中,读写锁很常见,因为读操作非常常见,读写锁可以把并发读之间的锁冲突的开销省下,对于性能提升显著
在标准库中,也提供了专门的类,实现读写锁(本质上还是系统提供的读写锁,提供的api)
6. 公平锁 和 非公平锁
这里的“公平”,是遵循先来后到的规则
公平锁:遵循先来后到的顺序,前面的释放锁,后面在拿到锁
非公平锁:站在系统原生的锁的角度,就是属于“非公平锁,系统线程的调度本身就是无序的随机的
synchronized 就是非公平锁
要想实现公平锁,就需要引入额外的数据结构(引入队列,记录每个线程先后顺序),才能实现公平锁
使用公平锁,天然就可以避免线程饿死的问题
6.可重入锁 和 不可重入锁
可重入锁:一个线程针对一把锁,连续加锁两次,不会死锁,这就是可重入锁
不可重入锁:一个线程针对一把锁,连续加锁两次,会死锁,这就是不可重入锁
synchronized 是可重入锁
系统自带的锁,是不可重入锁
可重入锁种需要记录持有锁的线程是谁,加锁的次数的计数