文章目录
- 公平和非公平锁
- 是什么
- 两者区别
- 题外话sync与reentrantlock
- 可重入锁和递归锁理论知识
- 是什么
- 可重入锁和递归锁代码验证
- 自旋锁理论知识
- 自旋锁代码验证-手写一个自旋锁
- 读写锁理论知识
- 读写锁代码验证
- CountdownLatch
- 枚举优化countdownlatch
- CyclicBarrier
- semaphore
公平和非公平锁
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM5EjM5YjNiNmYlVmY1I2NzYzX2IzNwkTMxIzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
是什么
Reentrantlock的构造方法如下:
空构造底层是非公平锁。相当于Reentrantlock(false)。
可以传入bool类型的参数,来改变锁的类型,是公平还是非公平。
公平和非公平,指的是排序策略。
公平锁:就是有个队列,执行讲究先来后到。
非公平锁:就是允许有后来的线程加塞执行。
优先级反转:后来的先执行。
饥饿现象:排在最后的总是得不到执行。
两者区别
题外话sync与reentrantlock
可重入锁和递归锁理论知识
reentrantlock:字面意思就是可重入锁。可重入锁就是递归锁。
是什么
进入家里门之后,再进厨房是不需要开锁的,跟这个一个道理。
这俩是典型的可重入锁(非公平)。最大作用是避免死锁。
可重入锁和递归锁代码验证
测试代码:
结果:
现象是同步方法可以进入同步方法。
案例二:证明reentrantlock也是重入锁。
代码补足:
测试代码+执行结果:
这个实验证明,reentrantlock也是重入锁。
类似的场景还有子类调用父类的,也可以证明。
深化理解:如果加两个锁呢?
首先,编译器没报错,语法是没有问题的。
运行,也是没有问题的。
说明只要你的锁匹配上有lock有unlock,几把都可以的。
但是如果缺一个unlock,没有全部配对上,语法是没问题的。但是执行就不行了,没有解锁!
此时程序卡死了!所以一定要加锁几次,就解锁几次!
理论+代码+总结。学习三板斧,受教了!
自旋锁理论知识
之前讲过的自旋锁的相关,atomic类是用的unsafe类+cas思想(自旋)。
生活中的case:自旋的反义词是阻塞。我在打电话,有人来问我问题,他就站我身边或者看到堵塞去忙自己的事 这两种选择。忙一会回来看看,忙一会回来看这种行为,就可以理解为自旋。这是老师的例子,我觉得不好。应该是频繁重新尝试获得锁,就像你在打电话,然后我不断的问:打完了吗?打完了吗?尝试着我想做的事情。
自旋锁代码验证-手写一个自旋锁
对引用类型的原子类引用,啥都不写,指向的就是null。
通过cas的思想,实现加锁与解锁的方法,当有第一个线程访问的时候,mylock的while判定是false(因为对引用类型的原子类引用,啥都不写,指向的就是null,所以compareAndSet的结果就是true,取反为false),就没有进入循环,相当于获得了锁,解锁的逻辑是用完了,把当前线程更新为null,可以被其他线程访问了。如果过程中有其他线程进入访问,就会进入while循环,从而不断尝试获得锁,而不是做线程切换。
测试代码:
运行效果:
BB通过在线程里自旋,完成多次尝试获得锁,而不是阻塞。可能会消耗性能。
读写锁理论知识
独占锁,也叫写锁。共享锁也叫读锁,可以被很多线程并发读。
锁的进化历程:
以前的锁,都是独占的,所以进化出了读写分离的读写锁。
很重要的一点:知识点一定要用代码证明!
读写锁代码验证
写法一:
原子性得到了保障,配合volatile,满足jmm模型的三大特性要求,但是读取的时候也独占就效率太低了。
题外话:缓存的三大接口:添加,读取,清空。
模拟一个不加锁的场景:
对于写操作,满足原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断。
测试代码:5个线程读,5个线程写,先写入再读取。执行结果:
会发现写入操作,中间被打断了很多次。这种情况传统锁,可以满足独占,但是业务场景需要同读同写,这样传统的锁就满足不了需求了,性能太差了。
加入读写锁:
运行结果:
效果对比:
CountdownLatch
作用:火箭发射倒计时
api翻译:countdownlatch在初始化的时候会被赋予一个计数,await方法会阻塞线程,直到这个计数减为0(类似火箭发射,燃料正常,逃逸仓正常,电压正常…)。
演示原生代码,遇到这种需要等着前面的子节点操作完了之后再执行操作的场景(类似火箭发射),不做处理:
结果是原地爆炸:
这种场景的解决方案就是countdownlatch。代码如下:
主线程堵塞,在每个线程里面减一,当count变成0之后,就释放锁,然后继续往下执行。
运行结果:
如果现在有这种需求,有id,有实际的名字,要求按照id,不做if判断的情况下,输出实际的名字在打印方法中。
上述需求的解决方案就是:枚举
枚举优化countdownlatch
这是一种把枚举当成数据库(k-v型)来用,也可以用redis缓存,但是用缓存会增加打开关闭的开销,也不值当的。这种需要对应的场景,用枚举是很好的选择,可以省去很多if判断。
枚举类的名字就是数据库的名字,然后每个枚举里面的内容就是一条数据。
设计枚举类:
根据id返回:
业务测试代码改为:
测试结果:
CyclicBarrier
用法:集齐7个龙珠就能召唤神龙。人到齐了才能开会。
代码案例:
先到的被堵塞。
semaphore
作用:抢车位,多个资源抢多个资源。
代码演示:6个车三个车位