只作一个浅显的解释作为区分,具体深入还请搜索相关博客。
可重入锁(又叫递归锁)
synchronized(隐式,自动释放)、 lock(显式,需要手动解锁和上锁)
进入第一个锁之后,后面的锁都可以进入。
可重入锁是一种特殊的互斥锁,它可以被同一个线程多次获取,而不会产生死锁。
- 首先它是互斥锁:任意时刻,只有一个线程锁。即假设A线程已经获取了锁,在A线程释放这个锁之前,B线程是无法获取到这个锁的,B要获取这个锁就会进入阻塞状态。
- 其次,它可以被同一个线程多次持有。即,假设A线程已经获取了这个锁,如果A线程在释放锁之前又一次请求获取这个锁,那么是能够获取成功的。
公平锁和非公平锁
非公平:new ReentrantLock() / ReentrantLock(false)
公平:new ReentrantLock( true)
- 公平锁:会考虑每个线程都会得到资源,不会饿死,但是效率慢
- 非公平锁:不考虑让别的线程获得资源,谁能抢到就是谁的,会导致别的线程饿死,效率高。
读写锁
ReentrantReadWriteLock
:它表示两个锁,一个是读操作相关的锁,称为 共享锁;一个是写相关的锁,称为 排他锁。
- 对共享资源有读和写的操作,读锁和写锁都会发生死锁。
- 读锁: 共享锁
- 写锁:独占锁。
- 特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公
平优于公平。
(2)重进入:读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为
读锁
锁降级
写锁降级为读锁。
读锁不能升级为写锁。
悲观锁和乐观锁
图解
- 悲观锁:每次都上锁,效率低
- 乐观锁:有个版本号,不同线程更改资源了,版本号不同会改失败。
CAS(Compare And Swap)就是将内存值更新为需要的值(包含三个操作数—— 内存位置的值(V)、预期原值(A)和新值(B)),但是有个条件,内存值V必须与期望值A相同。举个例子,内存值V、期望值A、更新值B,当V == A的时候将V更新为B。
参考
表锁、行锁
- 表锁:操作记录是,对数据库整张表都上锁,会发生 死锁
- 行锁:对表中一条记录上锁。
自旋锁
当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。
自旋锁在Java1.6中改为默认开启,并引入了自适应的自旋锁。
详情
适应性自旋锁
自适应意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间和锁的拥有者的状态共同决定。
如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很可能再次成功的,进而它将会允许线程自旋相对更长的时间。
如果对于某个锁,线程很少成功获得过,则会相应减少自旋的时间甚至直接进入阻塞的状态,避免浪费处理器资源。
锁粗化
锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。
简单来说,就是把锁的范围放大,但是放大之后要比之前的性能效率好。这时候就可以粗化
场景:一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。
public void doSomethingMethod(){
synchronized(lock){
//do some thing
}
//这是还有一些代码,做其它不需要同步的工作,但能很快执行完毕
synchronized(lock){
//do other thing
}
}
//粗化
public void doSomethingMethod(){
//进行锁粗化:整合成一次锁请求、同步、释放
synchronized(lock){
//do some thing
//做其它不需要同步但能很快执行完的工作
//do other thing
}
}
锁消除
不需要锁的情况,又要使用有锁的方法,这个时候用锁消除。
public class Demo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
int size = 10000;
for (int i = 0; i < size; i++) {
createStringBuffer("Hyes", "为分享技术而生");
}
long timeCost = System.currentTimeMillis() - start;
System.out.println("createStringBuffer:" + timeCost + " ms");
}
public static String createStringBuffer(String str1, String str2) {
StringBuffer sBuf = new StringBuffer();
sBuf.append(str1);// append方法是同步操作
sBuf.append(str2);
return sBuf.toString();
}
}
//append源码有锁,但实际上即使在并发环境也不需要
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
代码中createStringBuffer方法中的局部对象sBuf,就只在该方法内的作用域有效,不同线程同时调用createStringBuffer()方法时,都会创建不同的sBuf对象,因此此时的append操作若是使> > 用同步操作,就是白白浪费的系统资源。这时我们可以通过编译器将其优化,将锁消除,前提是java必须运行在server模式(server模式会比client模式作更多的优化),同时必须开启逃逸分析:
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis
表示开启逃逸分析,+EliminateLocks
表示锁消除。
参考
偏向锁
JDK6以上默认开启
当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的 ID,后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了
Original: https://www.cnblogs.com/CodeWater404/p/16338995.html
Author: CodeWater
Title: 各种锁
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714575/
转载文章受原作者版权保护。转载请注明原作者出处!